我正在寻找一种将新项目插入列表视图的方法,同时保持用户的滚动偏移量。基本上像拉动刷新后的Twitter提要:将新项目添加到顶部,同时保持滚动位置。然后,用户可以向上滚动以查看新添加的项目。
如果我只是在开始时用几个新项重建列表/滚动小部件,则它当然会跳转,因为滚动视图内容的高度增加了。不能估计这些新项目的高度来纠正跳跃,因为新项目的内容是可变的。 即使提供了在任意位置动态插入项目的方法的AnimatedList小部件,在插入索引0时也会跳转。
有关如何解决此问题的任何想法?也许使用Offstage小部件预先计算新物品的高度?
答案 0 :(得分:1)
最近遇到了这个问题:我有一个聊天滚动,该异步滚动根据滚动的方向加载上一条或下一条消息。 This解决方案为我解决了。
解决方案的想法如下。您创建两个SliverLists并将它们放入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中找到解决方案原型。
答案 1 :(得分:0)
答案 2 :(得分:0)
将Key
添加到列表项小部件。当您更改列表时,这将有助于Flutter ListView识别项目。
下面是示例虚拟代码:
ListView.builder(itemBuilder: (context, index) {
return ListBox(someData: someData, key: Key(someData.uniqueIdentifier),);
});
在上面的代码中,ListBox是一些自定义窗口小部件,在构造函数中具有 Key 并显示一些数据。如图所示,在创建对象时传递键的唯一标识符,这将使 ListView 可以识别列表中的项目。无需做任何事情,当列表发生变化时,ListView仍然能够识别滚动视图中的项目。
答案 3 :(得分:0)
我不知道您是否能够解决它……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。