Flutter ListView 跳转到顶部

spo*_*oss 7 dart firebase flutter google-cloud-firestore

我正在为我的应用创建一个新闻提要,当我向下滚动时,数据是从 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]),);
                  });
            },
          );
Run Code Online (Sandbox Code Playgroud)

Dmi*_*hin 8

问题原因:

\n\n
\n

ListView,并且ScrollViews一般来说,倾向于处理当前在屏幕上不可见的子项。当我们尝试滚动回子项时,子项将从头开始重新初始化。但在这种情况下,我们的孩子是一个 FutureBuilder;重新初始化它会再次创建一个进度指示器,仅持续一秒钟,然后再次创建页面。这会混淆滚动机制,让我们陷入不确定的境地。

\n
\n\n

解决方案

\n\n
\n

解决此问题的一种方法是确保进度指示器与页面大小完全相同,但在大多数情况下,这不太实用。所以,我们会采取一种效率较低的方法,但可以解决我们的问题;我们将阻止 ListView 处理子项。为了做到这一点,我们需要用FutureBuilder一个AutomaticKeepAliveClientMixin. 这种混合使孩子们要求父母让他们即使在屏幕外也能活下去,这将解决我们的问题。所以:

\n
\n\n
    \n
  1. 将代码中的 FutureBuilder 替换为 KeepAliveFutureBuilder。
  2. \n
  3. 创建 KeepAliveFutureBuilder 小部件:
  4. \n
\n\n\n\n
class KeepAliveFutureBuilder extends StatefulWidget {\n\n  final Future future;\n  final AsyncWidgetBuilder builder;\n\n  KeepAliveFutureBuilder({\n    this.future,\n    this.builder\n  });\n\n  @override\n  _KeepAliveFutureBuilderState createState() => _KeepAliveFutureBuilderState();\n}\n\nclass _KeepAliveFutureBuilderState extends State<KeepAliveFutureBuilder> with AutomaticKeepAliveClientMixin {\n  @override\n  Widget build(BuildContext context) {\n    return FutureBuilder(\n      future: widget.future,\n      builder: widget.builder,\n    );\n  }\n\n  @override\n  bool get wantKeepAlive => true;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
    \n
  • 这个小部件只是 FutureBuilder 的包装。它是一个 StatefulWidget,其 State 通过 AutomaticKeepAliveClientMixin 扩展了 State 类。
  • \n
  • 它实现了wantKeppAlive getter,并使其简单地返回true,以向ListView 表示我们希望该子项保持活动状态。
  • \n
\n


小智 6

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

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

  1. 将其ListView包裹起来SingleChildScrollView
  2. physics: const NeverScrollableScrollPhysics()如前所述设置ListView 的属性
  3. 设置shrinkWrap: true以避免渲染问题

或者

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

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

  • 这个解决方案非常适合我的情况,谢谢! (2认同)

Dav*_*ood 5

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

cacheExtent: estimatedCellHeight * numberOfItems,
Run Code Online (Sandbox Code Playgroud)

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

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