在 Flutter 中保持滚动视图偏移的同时添加列表视图项

Posted

技术标签:

【中文标题】在 Flutter 中保持滚动视图偏移的同时添加列表视图项【英文标题】:Prepend list view items while maintaining scroll view offset in Flutter 【发布时间】:2019-02-10 08:27:18 【问题描述】:

我正在寻找一种将新项目插入列表视图的方法,同时保持用户的滚动偏移量。基本上就像拉刷新后的推特提要:新项目被添加到顶部,而滚动位置保持不变。然后,用户只需向上滚动即可查看新添加的项目。

如果我只是在开始时用几个新项目重建列表/滚动小部件,它当然会跳跃,因为滚​​动视图内容的高度增加了。仅仅估计那些新项目的高度来纠正跳跃不是一种选择,因为新项目的内容是可变的。 即使是提供在任意位置动态插入项目的方法的 AnimatedList 小部件,在索引 0 处插入时也会跳转。

关于如何解决这个问题的任何想法?也许使用 Offstage 小部件预先计算新项目的高度?

【问题讨论】:

您能否看看这些链接是否有帮助:***,medium @Thanthu:谢谢,但是这些链接是关于在重建时记住滚动偏移量(例如在切换选项卡时),在我的情况下,它是关于在现有的新项目中添加新项目时保持用户感知的滚动偏移量列表。 请检查我刚刚发布的答案。它对我有用。如果您需要,我可以添加更多详细信息。 您能找到解决方案吗?我好像遇到了同样的问题 @NickMowen:很遗憾没有。我为它创建了一个问题:github.com/flutter/flutter/issues/21541 【参考方案1】:

我认为 reverse + lazyLoading 会帮助你。

反转列表:

ListView.builder(reverse: true, ...);

对于lazyLoading,请参考here。

【讨论】:

这可能是某些用例的解决方案,但在我的情况下,我的列表已经支持在底部延迟加载新项目。所以我需要twitter feed行为:在底部滚动时延迟加载(这很简单)在顶部刷新,但不会向上踢用户的滚动偏移(这似乎更难) . 抱歉。我误解了这个问题。 @florisvdg 你找到解决办法了吗?【参考方案2】:

我不知道你是否设法解决它...... Marcin Szalek 在他的blog 上发布了一个非常好的解决方案,关于实现无限动态列表。我试过了,它就像一个 ListView 的魅力。然后我尝试使用 AnimatedList 执行此操作,但遇到了您报告的相同问题(每次刷新后跳到顶部......)。无论如何,ListView 非常强大,应该可以为您解决问题! 代码是:

import 'dart:async';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      theme: new ThemeData(primarySwatch: Colors.blue),
      home: new MyHomePage(),
    );
  


class MyHomePage extends StatefulWidget 
  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  List<int> items = List.generate(10, (i) => i);
  ScrollController _scrollController = new ScrollController();
  bool isPerformingRequest = false;

  @override
  void initState() 
    super.initState();
    _scrollController.addListener(() 
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) 
        _getMoreData();
      
    );
  

  @override
  void dispose() 
    _scrollController.dispose();
    super.dispose();
  

  _getMoreData() async 
    if (!isPerformingRequest) 
      setState(() => isPerformingRequest = true);
      List<int> newEntries = await fakeRequest(
          items.length, items.length + 10); //returns empty list
      if (newEntries.isEmpty) 
        double edge = 50.0;
        double offsetFromBottom = _scrollController.position.maxScrollExtent -
            _scrollController.position.pixels;
        if (offsetFromBottom < edge) 
          _scrollController.animateTo(
              _scrollController.offset - (edge - offsetFromBottom),
              duration: new Duration(milliseconds: 500),
              curve: Curves.easeOut);
        
      
      setState(() 
        items.addAll(newEntries);
        isPerformingRequest = false;
      );
    
  

  Widget _buildProgressIndicator() 
    return new Padding(
      padding: const EdgeInsets.all(8.0),
      child: new Center(
        child: new Opacity(
          opacity: isPerformingRequest ? 1.0 : 0.0,
          child: new CircularProgressIndicator(),
        ),
      ),
    );
  

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: AppBar(
        title: Text("Infinite ListView"),
      ),
      body: ListView.builder(
        itemCount: items.length + 1,
        itemBuilder: (context, index) 
          if (index == items.length) 
            return _buildProgressIndicator();
           else 
            return ListTile(title: new Text("Number $index"));
          
        ,
        controller: _scrollController,
      ),
    );
  


/// from - inclusive, to - exclusive
Future<List<int>> fakeRequest(int from, int to) async 
  return Future.delayed(Duration(seconds: 2), () 
    return List.generate(to - from, (i) => i + from);
  );

可以找到包含整个课程的要点here。

【讨论】:

您应该将链接中的相关部分提供到您的答案中,以便将来如果博客文章被删除,读者仍然可以使用您的答案。 此解决方案将项目添加到列表末尾。 OP 要求一种在列表开头添加项目的方法(所以向上滚动,而不是向下滚动) 这个答案与@florisvdg 在问题中提出的完全相反。【参考方案3】:

最近遇到了这个问题:我有一个聊天滚动条,它根据滚动条的方向异步加载上一条或下一条消息。 This 解决方案为我解决了。 解决方案的思路如下。您创建两个 SliverList 并将它们放在 CustomScrollView 中。

CustomScrollView(
  center: centerKey,
  slivers: <Widget>[
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) 
            return Container(
              // Here we render elements from the upper group
              child: top[index]
            )
        
    ),
    SliverList(
      // Key parameter makes this list grow bottom
      key: centerKey,
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) 
            return Container(
              // Here we render elements from the bottom group
              child: bottom[index]
            )
        
    ),
)

第一个列表向上滚动,而第二个列表向下滚动。它们的偏移零点固定在同一点,从不移动。如果您需要添加一个项目,请将其推送到顶部列表,否则,将其推送到底部列表。这样他们的偏移量就不会改变,你的滚动视图也不会跳跃。 您可以在以下dartpad example找到解决方案原型。

UPD:修复了此 dartpad example 中的 null 安全问题。

【讨论】:

谢谢你,太好了。由于飞镖新的空安全性,飞镖垫抛出错误您可以将飞镖垫中的第 19 行更改为 MyStatefulWidget(Key?key) : super(key: key); @MikeMoy 感谢您的关注。修复了新 dartpad 文件中的问题。

以上是关于在 Flutter 中保持滚动视图偏移的同时添加列表视图项的主要内容,如果未能解决你的问题,请参考以下文章

Flutter - 在列表中添加新项目时保持滚动位置

向上滚动时保持 UICollectionView 中的滚动位置

Flutter - 可滚动页面中的列表视图

Flutter 在列表视图中显示网格视图

滚动侦听器不适用于 SingleChildScrollView Flutter 中的列表视图

JScrollPane 在顶部添加 JPanel 并保持当前滚动视图