【问题标题】:Flutter - ListView inside on a TabBarView loses its scroll positionFlutter - TabBarView 内部的 ListView 失去滚动位置
【发布时间】:2018-01-02 15:33:05
【问题描述】:

我有一个非常简单的 Flutter 应用,它有一个 TabBarView 和两个视图(Tab 1Tab 2),其中一个(Tab 1) 有一个带有许多简单文本小部件的 ListView,问题是在我向下滚动 Tab 1 的 ListView 元素后,如果我从 Tab 滑动1Tab 2 最后我从 Tab 2Tab 1Tab 1的ListView中之前的滚动位置会丢失。

代码如下:

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  TabController controller;

  @override
  void initState() {
    super.initState();
    controller = new TabController(
      length: 2,
      vsync: this,
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var tabs = <Tab>[
      new Tab(icon: new Icon(Icons.home), text: 'Tab 1'),
      new Tab(icon: new Icon(Icons.account_box), text: 'Tab 2')
    ];

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new TabBarView(controller: controller, children: <Widget>[
        new ListView(children: <Widget>[
          new Column(children: <Widget>[
            new Text('Data 1'),
            new Text('Data 2'),
            new Text('Data 3'),
            new Text('Data 4'),
            new Text('Data 5'),
            new Text('Data 6'),
            new Text('Data 7'),
            new Text('Data 8'),
            new Text('Data 9'),
            new Text('Data 10'),
            new Text('Data 11'),
            new Text('Data 12'),
            new Text('Data 13'),
            new Text('Data 14'),
            new Text('Data 15'),
            new Text('Data 16'),
            new Text('Data 17'),
            new Text('Data 18'),
            new Text('Data 19'),
            new Text('Data 20'),
            new Text('Data 21'),
            new Text('Data 22'),
            new Text('Data 23'),
            new Text('Data 24'),
            new Text('Data 25'),
            new Text('Data 26'),
            new Text('Data 27'),
            new Text('Data 28'),
            new Text('Data 29'),
            new Text('Data 30'),
            new Text('Data 31'),
            new Text('Data 32'),
            new Text('Data 33'),
            new Text('Data 34'),
            new Text('Data 35'),
            new Text('Data 36'),
            new Text('Data 37'),
            new Text('Data 38'),
            new Text('Data 39'),
            new Text('Data 40'),
            new Text('Data 41'),
            new Text('Data 42'),
            new Text('Data 43'),
            new Text('Data 44'),
            new Text('Data 45'),
            new Text('Data 46'),
            new Text('Data 47'),
            new Text('Data 48'),
          ])
        ]),
        new Center(child: new Text('Tab 2'))
      ]),
      bottomNavigationBar: new Material(
        color: Colors.deepOrange,
        child: new TabBar(controller: controller, tabs: tabs),
      ),
    );
  }
}

我什至将 TabBarView childrens(Tab 1 和 Tab 2)分隔在另一个类中,我注意到

@override
  Widget build(BuildContext context) {
  ...
}

每次我滑动到它的容器选项卡时,都会执行每个子项(Tab 1 和 Tab 2)的方法。

我的问题是:

1.- 即使我在标签之间移动,如何保持 ListView 的滚动?

2.- 有没有办法执行

@override
Widget build(BuildContext context) {

}

如果我将 TabBarView 子类(Tab 1 和 Tab 2)分离到另一个类,则只使用一次方法? 我的意思是,如果我必须在创建 Tab 1 和 Tab 2 时检索数据,我不想每次刷入 Tab 时都这样做。那会很昂贵。

3.- 一般来说,有没有办法防止每次滑动到该选项卡时重新构建选项卡视图(包括它的变量、数据等)?

提前致谢。

【问题讨论】:

    标签: listview scroll flutter


    【解决方案1】:

    有没有办法只执行一次 Widget build(BuildContext context) 方法...

    恕我直言,flutter 的想法是随时为重建做好准备。它应该很便宜。如果您有一些昂贵的操作,您可以使用State 来“缓存”结果。例如。您可以在initState 中进行网络请求,并在收到响应时通过setState 重建。对于选项卡,您可以在父窗口小部件中准备和保存数据。你可以在flutter tutorial找到更多关于管理状态的信息

    【讨论】:

    • 实际上,当您从“LEFT”选项卡滑动到“RIGHT”选项卡时,Gallery 应用程序 Tabs 演示不会在 ListViews 中保持滚动,我有最新版本的 Flutter Gallery 应用程序可用我的 Android 设备中的 Play Store 也有同样的问题。你测试过吗?关于“缓存”结果的状态,我现在将对此进行调查。非常感谢!
    • 我测试过,但做错了。返回后与列表重叠的应用栏存在问题。
    【解决方案2】:

    ???????????????

    Jordan Nelson's answer 是正确的。不要用我的。

    ???????????????

    1.- 即使我从一个选项卡移动到另一个选项卡,如何保持 ListView 的滚动?

    好吧,这并不像我想象的那么容易,但我想我做到了。

    我的想法是在 HomePageState 中保留 listview 的偏移量,当我们滚动 listview 时,我们只是从通知程序中获取偏移量并通过 setter 设置它(请让它更清洁并分享!)。

    然后,当我们重建列表视图时,我们只要求我们的主小部件给我们保存的偏移量,并通过 ScrollController 我们用该偏移量初始化列表。

    我还更改了您的列表视图,因为它有一个包含 50 个文本的列元素,以使用 50 个元素和一个文本。希望你不介意:)

    代码:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(new MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Flutter Demo',
          theme: new ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: new MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    typedef double GetOffsetMethod();
    typedef void SetOffsetMethod(double offset);
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => new _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      TabController controller;
      double listViewOffset=0.0;
    
      @override
      void initState() {
        super.initState();
        controller = new TabController(
          length: 2,
          vsync: this,
        );
      }
    
      @override
      void dispose() {
        controller.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        var tabs = <Tab>[
          new Tab(icon: new Icon(Icons.home), text: 'Tab 1'),
          new Tab(icon: new Icon(Icons.account_box), text: 'Tab 2')
        ];
    
        return new Scaffold(
          appBar: new AppBar(
            title: new Text(widget.title),
          ),
          body: new TabBarView(
          controller: controller,
          children: <Widget>[
            new StatefulListView(
              getOffsetMethod: () => listViewOffset,
              setOffsetMethod: (offset) => this.listViewOffset = offset,
            ),
            new Center(child: new Text('Tab 2'))
          ]),
          bottomNavigationBar: new Material(
            color: Colors.deepOrange,
            child: new TabBar(controller: controller, tabs: tabs),
          ),
        );
      }
    }
    
    class StatefulListView extends StatefulWidget {
      StatefulListView({Key key, this.getOffsetMethod, this.setOffsetMethod}) : super(key: key);
    
      final GetOffsetMethod getOffsetMethod;
      final SetOffsetMethod setOffsetMethod;
    
      @override
      _StatefulListViewState createState() => new _StatefulListViewState();
    }
    
    class _StatefulListViewState extends State<StatefulListView> {
    
      ScrollController scrollController;
    
      @override
      void initState() {
        super.initState();
        scrollController = new ScrollController(
          initialScrollOffset: widget.getOffsetMethod()
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return new NotificationListener(
          child: new ListView.builder(
            controller: scrollController,
            itemCount: 50,
            itemBuilder: (BuildContext context, int index) {
              return new Text("Data "+index.toString());
            },
          ),
          onNotification: (notification) {
            if (notification is ScrollNotification) {
              widget.setOffsetMethod(notification.metrics.pixels);
            }
          },
        );
      }
    }
    

    【讨论】:

    • 我真的对ScrollController一无所知,没有阅读文档是我的错。毫无疑问,您的回答解决了我的问题,非常感谢!顺便说一句,我认为你获得偏移量并分配它的方式已经非常干净了。
    • 我稍微修改了你的代码,以便能够在更多地方使用它,并创建了一篇关于它的中等帖子 - medium.com/@boldijar.paul/…
    【解决方案3】:

    如果你给每个 TabBarView 一个 PageStorageKey 滚动偏移量将被保存。查看有关PageStorageKey here 的更多信息。

    【讨论】:

    • 如果您滚动很多元素,我在使用此解决方案的选项卡切换时会有很大的延迟。有什么想法吗?
    • @Kuva 你使用的是 ListView() 还是 ListView.builder()?
    • 这是一个超级简单的工作解决方案,尽管我在切换屏幕时也遇到了明显滞后的问题(我正在使用 ListView.builder())。
    • 请记住,导航器会自动为每个路由添加一个 PageStorage,因此如果您丢失滚动位置,您可能需要创建一个静态 PageStorageBucket() 并在 PageStorage 中使用它来包装您的小部件树在路线内。
    【解决方案4】:

    更具体地说,您可以将 PageStorageKey 与任何可滚动视图一起使用以保持滚动位置,例如:

    new ListView.builder(key: new PageStorageKey('myListView'), ...)
    

    【讨论】:

    • 如果您滚动很多元素,我在使用此解决方案的选项卡切换时会有很大的延迟。有什么想法吗?
    【解决方案5】:

    输出:


    代码:

    @override
    Widget build(BuildContext context) {
      return DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            title: Text("PageStorageKey"),
            bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.looks_one), text: "List1"),
                Tab(icon: Icon(Icons.looks_two), text: "List2"),
              ],
            ),
          ),
          body: TabBarView(
            children: [
              _buildList(key: "key1", string: "List1: "),
              _buildList(key: "key2", string: "List2: "),
            ],
          ),
        ),
      );
    }
    
    Widget _buildList({String key, String string}) {
      return ListView.builder(
        key: PageStorageKey(key),
        itemBuilder: (_, i) => ListTile(title: Text("${string} ${i}")),
      );
    }
    

    【讨论】:

    • 你知道在滚动时改变每个部分的标签项吗?像这样youtube.com/watch?v=LrOR5QOCHBI
    • @VinothVino 在ListView 中使用ScrollController,逻辑是检查偏移量是否大于某个高度,您只需在@987654328 上执行TabController.animateTo @
    • 我想在该部分靠近标签栏时更改标签。列表项是动态的 我不知道如何计算列表项每个部分的高度。好吧,让我试试。谢谢老哥的回复。
    • 最佳答案,key:PageStorageKey('myOwnKey') 是答案
    • 非常感谢您的解决方案。我认为这是最简单的方法。
    【解决方案6】:

    无法添加评论,所以留下作为答案。

    如果您使用PageStorageKeyListView 中有很多项目,向下滚动很多项目并且标签切换有延迟,解决方案是提供itemExtentListView

    据我了解,没有itemExtent ListView 不知道哪些项目会立即显示,因为使用PageStorageKey 仅以像素为单位兑现位置,而ListView 需要计算从顶部滚动的所有项目的高度。另一方面,用itemExtentListView可以很方便的计算item来展示,例如:index = cashedPositionInPixels / itemExtent

    【讨论】:

      【解决方案7】:

      其实你不需要PageStorageKey。问题是当您滑动到 tab2 然后向后滑动时,tab1 小部件将被重建。所以你失去了你的位置。最简单的解决方案是使用 AutomaticKeepAliveClientMixin 并覆盖 "wantToKeepAlive" 方法。然后 TabbarView 将自动保存在内存中,不会被重建。这样问题就解决了。更多详情可以从https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html看到

      【讨论】:

      猜你喜欢
      • 2019-09-17
      • 2019-09-07
      • 2020-01-02
      • 2021-07-24
      • 2019-06-01
      • 2019-01-17
      • 2018-06-03
      • 2018-11-13
      • 2010-12-05
      相关资源
      最近更新 更多