Dart 在颤振中保持滚动视图偏移的同时预先显示列表视图项

Dart 在颤振中保持滚动视图偏移的同时预先显示列表视图项,dart,flutter,Dart,Flutter,我正在寻找一种在保持用户滚动偏移量的同时将新项目插入列表视图的方法。基本上就像推拉刷新后的twitter提要:新项目添加到顶部,同时保持滚动位置。用户只需向上滚动即可查看新添加的项目 如果我只是在开始时用几个新项目重新构建列表/滚动小部件,它当然会跳起来,因为滚动视图内容的高度增加了。仅仅估计这些新项目的高度以纠正跳转不是一个选项,因为新项目的内容是可变的。 即使提供在任意位置动态插入项的方法的AnimatedList小部件在索引0处插入时也会跳转 关于如何处理这个问题有什么想法吗?也许可以使用

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

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


关于如何处理这个问题有什么想法吗?也许可以使用后台小部件预先计算新项目的高度?

我认为反向+懒散加载将对您有所帮助

撤销列表:

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

有关懒散的问题,请参阅。

我不知道您是否设法解决了它。。。Marcin Szalek在他的网站上发布了一个关于实现无限动态列表的非常好的解决方案。我试过了,效果很好,有一个列表视图。然后我尝试用一个动画列表来做,但遇到了与您报告的相同的问题(每次刷新后跳到顶部…)。无论如何,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);
  });
}
导入'dart:async';
进口“包装:颤振/材料.省道”;
void main()=>runApp(新的MyApp());
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回新材料PP(
主题:新主题数据(原始样本:颜色。蓝色),
主页:新建MyHomePage(),
);
}
}
类MyHomePage扩展StatefulWidget{
@凌驾
_MyHomePageState createState()=>\u MyHomePageState();
}
类_MyHomePageState扩展状态{
列表项=列表.生成(10,(i)=>i);
ScrollController_ScrollController=新的ScrollController();
bool isPerformingRequest=false;
@凌驾
void initState(){
super.initState();
_scrollController.addListener((){
如果(_scrollController.position.pixels==
_scrollController.position.maxScrollExtent){
_getMoreData();
}
});
}
@凌驾
无效处置(){
_scrollController.dispose();
super.dispose();
}
_getMoreData()异步{
如果(!isPerformingRequest){
setState(()=>isPerformingRequest=true);
列出新条目=等待伪造请求(
items.length,items.length+10);//返回空列表
if(newEntries.isEmpty){
双边缘=50.0;
double offsetFromBottom=\u scrollController.position.maxScrollExtent-
_滚动控制器.position.pixels;
if(从底部偏移<边缘){
_scrollController.animateTo(
_scrollController.offset-(边缘-从底部偏移),
持续时间:新的持续时间(毫秒:500),
曲线:Curves.easeOut);
}
}
设置状态(){
items.addAll(新条目);
isPerformingRequest=false;
});
}
}
小部件_buildProgressIndicator(){
返回新的填充(
填充:常数边集全部(8.0),
孩子:新中心(
子对象:新的不透明度(
不透明度:iPerformingRequest?1.0:0.0,
子项:新的CircularProgressIndicator(),
),
),
);
}
@凌驾
小部件构建(构建上下文){
归还新脚手架(
appBar:appBar(
标题:文本(“无限列表视图”),
),
正文:ListView.builder(
itemCount:items.length+1,
itemBuilder:(上下文,索引){
if(index==items.length){
返回_buildProgressIndicator();
}否则{
返回列表(标题:新文本(“数字$index”);
}
},
控制器:\ u滚动控制器,
),
);
}
}
///从包容到排斥
Future fakeRequest(int-from,int-to)异步{
返回未来。延迟(持续时间(秒:2),(){
返回列表.generate(to-from,(i)=>i+from);
});
}

可以找到包含整个类的要点。

最近遇到了这个问题:我有一个聊天卷轴,它根据卷轴的方向异步加载上一条或下一条消息。解决方案对我有效
解决方案的想法如下。创建两个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]
            )
        }
    ),
)
CustomScrollView(
中心:中心键,
条子:[
银表(
代表:SliverChildBuilderDelegate(
(BuildContext上下文,int索引){
返回容器(
//在这里,我们渲染来自上层组的元素
child:top[索引]
)
}
),
银表(
//关键参数使此列表增长到底部
键:中心键,
代表:SliverChildBuilderDelegate(
(BuildContext上下文,int索引){
返回容器(
//这里我们渲染底部组中的元素
子项:底部[索引]
)
}
),
)
第一个列表向上滚动,而第二个列表向下滚动。它们的偏移零点固定在同一点上,永远不会移动。如果需要预先添加项目,请将其推到顶部列表,否则,将其推到底部列表。这样,它们的偏移量不会改变,滚动视图也不会跳转。

您可以在以下内容中找到解决方案原型。

您是否可以查看这些链接是否有用:,@Thanthu:谢谢,但是这些链接是关于在重建时记住滚动偏移的(例如切换选项卡时),在我的例子中,它是关于在现有列表中预编新项目时保持用户感知的滚动偏移量。请检查我刚刚发布的答案。它对我有用。如果您需要,我可以添加更多详细信息。您能找到吗