更改选项卡时如何让 Flutter ScrollController 保存 ListView.builder() 的位置? [英] How to get Flutter ScrollController to save position of ListView.builder() when changing tabs?

查看:24
本文介绍了更改选项卡时如何让 Flutter ScrollController 保存 ListView.builder() 的位置?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我用 2 个选项卡制作了一个简单的示例,每个选项卡都包含一个 ListView 构建器.我的目标是能够在第一个列表视图中滚动,切换到第二个选项卡,然后切换回第一个选项卡并查看与之前相同的滚动位置.

I have made a simple example with 2 tabs, each containing a ListView builder. My goal is to be able to scroll in the first list view, switch to the 2nd tab, and then switch back to the first and see the same scroll position from before.

我已经尝试将键添加到每个列表视图中,但这只是一个猜测,因为我并不完全理解键.这没有帮助.

I have tried adding Keys to each of the list views, but that was only a guess as I don't fully understand keys. That didn't help.

为什么 ScrollControllers 不保存滚动位置?

Why don't the ScrollControllers save the scroll position?

这是 main.dart 的示例:

Here is the example main.dart:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  ScrollController controllerA = ScrollController(keepScrollOffset: true);
  ScrollController controllerB = ScrollController(keepScrollOffset: true);
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: <Widget>[
              Text('controllerA'),
              Text('controllerB'),
            ],
          ),
        ),
        body: TabBarView(
          children: <Widget>[
            ListView.builder(
                controller: controllerA,
                itemCount: 2000,
                itemBuilder: (context, i) {
                  return ListTile(
                      title: Text(
                    i.toString(),
                    textScaleFactor: 1.5,
                    style: TextStyle(color: Colors.blue),
                  ));
                }),
            ListView.builder(
                controller: controllerB,
                itemCount: 2000,
                itemBuilder: (context, i) {
                  return Card(
                    child: ListTile(
                      title: Text(i.toString()),
                    ),
                  );
                }),
          ],
        ),
      ),
    );
  }
}

这是我想要的一个 hacky 但有效的示例.但这感觉不是正确的方法,因为它每帧都重建两个控制器.

Here is a hacky but working example of what I want. This doesn't feel like the correct way to do this though, as its rebuilding both controllers every frame.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  double offsetA = 0.0;
  double offsetB = 0.0;

  @override
  Widget build(BuildContext context) {
    ScrollController statelessControllerA =
        ScrollController(initialScrollOffset: offsetA);
    statelessControllerA.addListener(() {
      setState(() {
        offsetA = statelessControllerA.offset;
      });
    });

    ScrollController statelessControllerB =
        ScrollController(initialScrollOffset: offsetB);
    statelessControllerB.addListener(() {
      setState(() {
        offsetB = statelessControllerB.offset;
      });
    });

    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: <Widget>[
              Text('controllerA'),
              Text('controllerB'),
            ],
          ),
        ),
        body: TabBarView(
          children: <Widget>[
            ListView.builder(
                controller: statelessControllerA,
                itemCount: 2000,
                itemBuilder: (context, i) {
                  return ListTile(
                      title: Text(
                    i.toString(),
                    textScaleFactor: 1.5,
                    style: TextStyle(color: Colors.blue),
                  ));
                }),
            ListView.builder(
                controller: statelessControllerB,
                itemCount: 2000,
                itemBuilder: (context, i) {
                  return Card(
                    child: ListTile(
                      title: Text(i.toString()),
                    ),
                  );
                }),
          ],
        ),
      ),
    );
  }
}

推荐答案

您可以使用 AutomaticKeepAliveClientMixin 在 Tab View 中持久化状态.

You can use AutomaticKeepAliveClientMixin to persist the states in Tab View.

举例

class GetListView extends StatefulWidget{
  @override
  State<StatefulWidget> createState() =>_GetListViewState();

}

class _GetListViewState extends State<GetListView> with AutomaticKeepAliveClientMixin<GetListView>{

  @override
  Widget build(BuildContext context){
    return ListView.builder(

                itemCount: 2000,
                itemBuilder: (context, i) {
                  return ListTile(
                      title: Text(
                    i.toString(),
                    textScaleFactor: 1.5,
                    style: TextStyle(color: Colors.blue),
                  ));
                });
  }

  @override
  bool get wantKeepAlive => true;

} 

TabBarView 的子节点中不要使用 ListView.builder,而是使用 GetListView.

Instead of using ListView.builder in childern of TabBarView use GetListView.

举例

TabBarView(
          children: <Widget>[
            GetListView(),
            ListView.builder(
                controller: controllerB,
                itemCount: 2000,
                itemBuilder: (context, i) {
                  return Card(
                    child: ListTile(
                      title: Text(i.toString()),
                    ),
                  );
                }),
          ],
        ),
      )

实现此目的的第二种方法是使用 PageStorageKey.PageStorageKey 被 Scrollables 用来保存滚动偏移量.每次滚动完成时,可滚动的页面存储都会更新.

The second way to achieve this is by using PageStorageKey. PageStorageKey is used by Scrollables to save the scroll offset. Each time a scroll completes, the scrollable's page storage is updated.

举例

 ListView.builder(
                key: PageStorageKey<String>('controllerA'),
                controller: statelessControllerA,
                itemCount: 2000,
                itemBuilder: (context, i) {
                  print("Rebuilded 1");
                  return ListTile(
                      title: Text(
                    i.toString(),
                    textScaleFactor: 1.5,
                    style: TextStyle(color: Colors.blue),
                  ));
                }),

注意:在第二个示例中,每次都会使用特定的滚动偏移量重建小部件.建议使用第一种方案.

Note: In the second example the widgets will be rebuilded everytime with a specific scroll offset. It's recommended to use the first solution.

这篇关于更改选项卡时如何让 Flutter ScrollController 保存 ListView.builder() 的位置?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆