【问题标题】:Flutter ListView Jumps To TopFlutter ListView 跳转到顶部
【发布时间】:2019-01-03 07:51:03
【问题描述】:

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

Video demonstrating the irregular scrolling behaviour

这是我的代码

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

【问题讨论】:

  • 你能把return FeedItem2() 换成return Container(padding: EdgeInsets.all(50.0), child: Text("test")); 看看问题是否消失了吗?如果是这样,它可能指向FeedItem2 小部件是问题,在这种情况下,我们需要查看该小部件的代码。我假设小部件正在做一些动态的事情
  • 为了好玩,尝试将cacheExtent: 10000.0 传递给 ListView 工厂构造函数。如果我们在滚动浏览当前可见的内容时遇到 ListView 如何处理和重新初始化小部件的问题,这可能有助于我们隔离
  • 也许确保 FeedItem2 始终保持相同的高度(在第一次渲染和任何后续渲染时,例如加载图像)。因为动态更改列表视图中元素的高度可能会导致一些屏幕外元素初始化,这反过来又会将它们推到足够远的位置以进行处理。我无法从视频中完全判断是否是这种情况. FWIW
  • 对我来说,解决方案是结合 cacheExtent(任何合理的像素数)和 shrinkWrap: false

标签: firebase dart google-cloud-firestore flutter


【解决方案1】:

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

方案一:(如果ListView的高度是动态的)

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

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

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

【讨论】:

  • 添加 NeverScrollableScrollPhysics() 可以避免滚动,我认为这是一种反模式!
  • NeverScrollableScrollPhysics() 当您在另一个 ScrollView 中使用 ScrollView 时,应为子项使用。在上面我们不确定 ListView 元素高度的情况下,我们应该使用上面用 SingleChildScrollView() 包裹它的解决方案。这将解决这种情况,因为它将采用 Parent 的 ScrollController 并忽略 ListView 的 ScrollController。这不会在您每次向上滚动时将滚动条捕捉到顶部。
  • Anhaa,我明白了,因为它在 SingleCh........ 对吗?为什么不在 SingleChi....里面放一个 Column 呢?帮助我理解@SankarArumugam
  • @emanuel 您可以将列放在 SingleChildScrollView 中。但这里的问题是在使用 ListView 时解决问题。就这样完成了
  • 安哈,谢谢
【解决方案2】:

只需将最顶层的小部件包裹在 SingleChildScrollView() 中即可。

【讨论】:

    【解决方案3】:

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

    cacheExtent: estimatedCellHeight * numberOfItems,
    

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

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

    【讨论】:

      【解决方案4】:

      问题原因:

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

      Solution:

      解决此问题的一种方法是确保进度指示器具有与页面完全相同的大小,但在大多数情况下,这不太实用。因此,我们将采用一种效率较低但可以解决我们的问题的方法;我们将阻止 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 表示我们希望这个孩子保持活动状态。

      【讨论】:

      • 但是对于一个很长的列表(想想你曾经收到的所有电子邮件的列表),这并不实用,是吗?屏幕外视图被丢弃是有原因的。
      • 同意。恕我直言。最好的方法是存储图像大小并使用此信息来避免问题。
      猜你喜欢
      • 2019-10-03
      • 2019-12-21
      • 1970-01-01
      • 1970-01-01
      • 2015-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多