繁体   English   中英

Flutter ListView 跳转到顶部

[英]Flutter ListView Jumps To Top

我正在为我的应用创建一个新闻提要,当我向下滚动时,数据是从 Firestore 获取的。 一旦我向上滚动,列表视图就会直接捕捉到最顶部,跳过中间的所有其他项目。 如果我使用静态数据加载列表视图,列表视图工作正常,但是当我从 Firestore 提取数据时,问题再次出现。 我不确定是什么导致了这种行为。

演示不规则滚动行为的视频

这是我的代码

return StreamBuilder(
            stream: Firestore.instance.collection(Constants.NEWS_FEED_NODE)
                .document("m9yyOjsPxV06BrxUtHdp").
            collection(Constants.POSTS_NODE).orderBy("timestamp",descending: true).snapshots(),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              if (!snapshot.hasData)
                return Center(child: CircularProgressIndicator(),);

              if (snapshot.data.documents.length == 0)
                return Container();
              //after getting the post ids, we then make a call to FireStore again
              //to retrieve the details of the individual posts
              return ListView.builder(
                  itemCount: snapshot.data.documents.length,
                  itemBuilder: (_, int index) {
                    return FeedItem2(feed: Feed.fireStore(
                        snapshot: snapshot.data.documents[index]),);
                  });
            },
          );

刚刚在使用 Flutter 1.17.3 时遇到了同样的问题。 基于@Ashton-Thomas 的评论: Flutter ListView Jumps To Top我补充说:

cacheExtent: estimatedCellHeight * numberOfItems,

到我的ListView构造函数,问题就消失了。 它也大大提高了整个列表的响应能力。

注意:我的列表中只有大约 50 个项目,所以cacheExtent不是很高。 如果您有数百/数千个项目,则可能需要进行调整。

问题原因:

ListView和一般的ScrollViews倾向于处理当前在屏幕上不可见的子项。 当我们尝试回滚到孩子时,孩子会从头开始重新初始化。 但在这种情况下,我们的孩子是一个 FutureBuilder; 重新初始化它会在一秒钟内再次创建一个进度指示器,然后再次创建页面。 这混淆了滚动机制,让我们以不确定的方式四处游荡。

解决方案

解决此问题的一种方法是确保进度指示器具有与页面完全相同的大小,但在大多数情况下,这不太实用。 因此,我们将求助于一种效率较低的方法,但这将解决我们的问题; 我们将阻止 ListView 处理孩子。 为了做到这一点,我们需要用AutomaticKeepAliveClientMixin包装每个孩子——也就是每个FutureBuilder 这个mixin让孩子们要求他们的父母即使在屏幕外也让他们活着,这将解决我们的问题。 所以:

  1. 将代码中的 FutureBuilder 替换为 KeepAliveFutureBuilder。
  2. 创建 KeepAliveFutureBuilder 小部件:
class KeepAliveFutureBuilder extends StatefulWidget {

  final Future future;
  final AsyncWidgetBuilder builder;

  KeepAliveFutureBuilder({
    this.future,
    this.builder
  });

  @override
  _KeepAliveFutureBuilderState createState() => _KeepAliveFutureBuilderState();
}

class _KeepAliveFutureBuilderState extends State<KeepAliveFutureBuilder> with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: widget.future,
      builder: widget.builder,
    );
  }

  @override
  bool get wantKeepAlive => true;
}
  • 这个小部件只是 FutureBuilder 的包装器。 它是一个 StatefulWidget,其 State 使用 AutomaticKeepAliveClientMixin 扩展了 State 类。
  • 它实现了 wantKeppAlive getter,并使其简单地返回 true,以向 ListView 表示我们希望这个孩子保持活动状态。

只需将最顶部的小部件包装在 SingleChildScrollView() 中。

这个问题有2个解决方案:

解决方案1:(如果ListView的高度是动态的)

  1. SingleChildScrollView包裹ListView
  2. 设置physics: const NeverScrollableScrollPhysics()的 ListView 的physics: const NeverScrollableScrollPhysics()属性
  3. 设置shrinkWrap: true以避免渲染问题

或者

解决方案 2:(如果 ListView 的高度已知或可预测)

  • cacheExtent:设置为 ListView 的高度 - 它是一个double cacheExtent:值。
  • 例如 - 如果 ListView 中存在 10 个项目并且每个项目的高度为 20.0,则给cacheExtent: 200.0

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM