Flutter - TabBarView内部的ListView丢失其滚动位置

时间:2017-07-27 04:56:04

标签: listview scroll flutter

我有一个非常简单的Flutter应用程序,带有 TabBarView ,有两个视图( Tab 1 Tab 2 ),其中一个(< strong>选项卡1 )有一个带有许多简单文本窗口小部件的ListView,问题是在我向下滚动选项卡1 的ListView元素后,如果我从选项卡滑动1 标签2 ,最后我从中滑动 标签2 标签1 标签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 儿童(标签1和标签2)分开了另一个类,我注意到了

@override
  Widget build(BuildContext context) {
  ...
}
每次我轻扫它的容器标签时,都会执行每个孩子的方法(标签1和标签2)。

我的问题是:

1.-即使我从标签移动到标签,如何保持 ListView 的滚动?

2.-有没有办法执行

@override
Widget build(BuildContext context) {

}

方法只有一次,如果我将 TabBarView子项(标签1和标签2)分开到另一个类? 我的意思是,如果我必须在创建选项卡1和选项卡2时检索数据,我不希望每次刷入选项卡时都这样做。这将是昂贵的。

3.-通常,有没有办法防止每次刷到该标签时重建标签视图(包括它的变量,数据等)?

提前谢谢。

7 个答案:

答案 0 :(得分:20)

如果为每个TabBarView提供一个PageStorageKey,则将保存滚动偏移量。查看有关PageStorageKey here的更多信息。

答案 1 :(得分:8)

1.-即使我从标签移动到标签,如何保持ListView的滚动?

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

我的想法是在HomePageState中保持listview的偏移量,当我们滚动listview时,我们只是从notifier获得偏移并通过setter设置它(请让它更清晰并分享!)。

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

我还更改了listview,因为它有一个包含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);
        }
      },
    );
  }
}

答案 2 :(得分:8)

更具体地说,您可以在任何可滚动视图中使用PageStorageKey来保持滚动位置,例如:

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

答案 3 :(得分:6)

输出:

enter image description here


代码:

@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}")),
  );
}

答案 4 :(得分:3)

实际上,您不需要PageStorageKey。问题在于,当您滑动到tab2然后向后滑动时,将重新构建tab1小部件。因此,您失去了职位。最简单的解决方案是使用AutomaticKeepAliveClientMixin并覆盖“ wantToKeepAlive”方法。然后TabbarView将自动保存在内存中,并且不会重建。因此问题将得到解决。您可以从https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html

查看更多详细信息

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

无法添加评论,因此将其保留为答案。

如果您使用PageStorageKey,在ListView中有很多项目,向下滚动很多项目并滞后于选项卡开关,则解决方案是向{{1 }}。

据我了解,没有itemExtent ListView不知道立即显示哪些项目,因为使用itemExtent仅兑现了以像素为单位的位置,并且ListView需要计算高度所有项目均从顶部滚动。另一方面,使用PageStorageKey ListView可以轻松计算要显示的项目,例如:itemExtent