我有一个可滚动项ListView,其中项目的数量可以动态更改.每当一个新项目添加到列表的末尾,我想以编程方式滚动ListView到最后.(例如,聊天消息列表,最后可以添加新消息)
我的猜测是我需要ScrollController在我的State对象中创建一个并将其手动传递给ListView构造函数,以便稍后在控制器上调用animateTo()/ jumpTo()method.但是,由于我无法轻易确定最大滚动偏移,因此似乎不可能简单地执行一种scrollToEnd()操作(而我可以轻松地传递0.0以使其滚动到初始位置).
有没有一种简单的方法来实现这一目标?
使用reverse: true对我来说不是一个完美的解决方案,因为当只有少量项目适合ListView视口时,我希望项目在顶部对齐.
Col*_*son 46
如果您使用收缩包装ListView带reverse: true,它滚动到0.0会做你想要什么.
import 'dart:collection';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Example',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _messages = <Widget>[new Text('hello'), new Text('world')];
ScrollController _scrollController = new ScrollController();
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Container(
decoration: new BoxDecoration(backgroundColor: Colors.blueGrey.shade100),
width: 100.0,
height: 100.0,
child: new Column(
children: [
new Flexible(
child: new ListView(
controller: _scrollController,
reverse: true,
shrinkWrap: true,
children: new UnmodifiableListView(_messages),
),
),
],
),
),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: () {
setState(() {
_messages.insert(0, new Text("message ${_messages.length}"));
});
_scrollController.animateTo(
0.0,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 300),
);
}
),
);
}
}
Run Code Online (Sandbox Code Playgroud)
Cop*_*oad 38
使用ScrollController.jumpTo()或ScrollController.animateTo()方法来实现这一目标.
这是代码片段(1秒后,ListView将滚动到底部)
class _HomePageState extends State<HomePage> {
ScrollController _controller = ScrollController();
@override
Widget build(BuildContext context) {
Timer(Duration(milliseconds: 1000), () => _controller.jumpTo(_controller.position.maxScrollExtent));
return ListView.builder(
controller: _controller,
itemCount: 50,
itemBuilder: (context, index) => ListTile(title: Text("ListTile"),));
}
}
Run Code Online (Sandbox Code Playgroud)
小智 17
listViewScrollController.animateTo(listViewScrollController.position.maxScrollExtent) 是最简单的方法。
小智 11
为了获得完美的结果,我将 Colin Jackson 和 CopsOnRoad 的答案结合如下:
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 500),
);
Run Code Online (Sandbox Code Playgroud)
小智 10
不要将 widgetBinding 放在 initstate 中,而是需要将其放在从数据库获取数据的方法中。例如,像这样。如果放入 initstate,则scrollcontroller不会附加到任何列表视图。
Future<List<Message>> fetchMessage() async {
var res = await Api().getData("message");
var body = json.decode(res.body);
if (res.statusCode == 200) {
List<Message> messages = [];
var count=0;
for (var u in body) {
count++;
Message message = Message.fromJson(u);
messages.add(message);
}
WidgetsBinding.instance
.addPostFrameCallback((_){
if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
});
return messages;
} else {
throw Exception('Failed to load album');
}
}
Run Code Online (Sandbox Code Playgroud)
虽然所有答案都产生了预期的效果,但我们应该在这里做一些改进。
首先,在大多数情况下(谈到自动滚动)使用 postFrameCallbacks 是无用的,因为在 ScrollController 附件(由attach方法生成)之后可以呈现一些东西,控制器将滚动到他知道的最后一个位置,并且该位置不能您认为的最新情况。
使用reverse:true应该是一个很好的技巧来“拖尾”内容,但物理将被颠倒,因此当您尝试手动移动滚动条时,您必须将其移动到另一侧 - > BAD UX。
在设计图形界面时使用计时器是一种非常糟糕的做法 -> 计时器在用于更新/生成图形工件时是一种病毒。
无论如何谈论这个问题,完成任务的正确方法是使用jumpTo带有hasClients方法的方法作为保护。
是否有任何 ScrollPosition 对象使用 attach 方法将自己附加到 ScrollController。如果为 false,则不得调用与 ScrollPosition 交互的成员,例如 position、offset、animateTo 和 jumpTo
用代码说话简单地做这样的事情:
if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
Run Code Online (Sandbox Code Playgroud)
无论如何,这段代码仍然不够,即使可滚动不在屏幕末尾,该方法也会被触发,因此如果您手动移动栏,该方法将被触发并执行自动滚动。
我们可以做得更好,在听众的帮助下和一些 bool 就可以了。
我正在使用这种技术在 SelectableText 中可视化大小为 100000 的 CircularBuffer 的值,并且内容不断正确更新,自动滚动非常流畅,即使对于非常非常长的内容也没有性能问题。也许正如有人在其他答案中所说的那样,该animateTo方法可以更流畅、更可定制,所以请随意尝试。
ScrollController _scrollController = new ScrollController();
bool _firstAutoscrollExecuted = false;
bool _shouldAutoscroll = false;
Run Code Online (Sandbox Code Playgroud)
void _scrollToBottom() {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
Run Code Online (Sandbox Code Playgroud)
void _scrollListener() {
_firstAutoscrollExecuted = true;
if (_scrollController.hasClients && _scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
_shouldAutoscroll = true;
} else {
_shouldAutoscroll = false;
}
}
Run Code Online (Sandbox Code Playgroud)
initState:@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
}
Run Code Online (Sandbox Code Playgroud)
dispose:@override
void dispose() {
_scrollController.removeListener(_scrollListener);
super.dispose();
}
Run Code Online (Sandbox Code Playgroud)
_scrollToBottom根据您的逻辑和需求,在您的setState:setState(() {
if (_scrollController.hasClients && _shouldAutoscroll) {
_scrollToBottom();
}
if (!_firstAutoscrollExecuted && _scrollController.hasClients) {
_scrollToBottom();
}
});
Run Code Online (Sandbox Code Playgroud)
解释
_scrollToBottom()为了避免代码重复;_scrollListener()和我们连接它的_scrollController在initState- >后的第一时间将被触发,该滚动条会移动。在此侦听器中,我们更新 bool 值的值_shouldAutoscroll以了解滚动条是否位于屏幕底部。dispose只是为了确保在小部件处理后不会做无用的事情。setState确定_scrollController已附加并且位于底部(检查 的值shouldAutoscroll)时,我们可以调用_scrollToBottom(). _scrollToBottom()短路 的值_firstAutoscrollExecuted。_controller.jumpTo(_controller.position.maxScrollExtent);
_controller.animateTo(_controller.position.maxScrollExtent);
Run Code Online (Sandbox Code Playgroud)
这些调用对于动态大小的项目列表效果不佳。我们当时不知道您调用jumpTo()列表的长度,因为所有项目都是可变的,并且是在我们向下滚动列表时延迟构建的。
这可能不是明智的方法,但作为最后的手段,您可以执行以下操作:
Future scrollToBottom(ScrollController scrollController) async {
while (scrollController.position.pixels != scrollController.position.maxScrollExtent) {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
await SchedulerBinding.instance!.endOfFrame;
}
}
Run Code Online (Sandbox Code Playgroud)
当我使用StreamBuilder小部件从数据库中获取数据时,我遇到了这个问题。我把它WidgetsBinding.instance.addPostFrameCallback放在小部件的方法之上build,它不会一直滚动到最后。我通过这样做修复了它:
...
StreamBuilder(
stream: ...,
builder: (BuildContext context, AsyncSnapshot snapshot) {
// Like this:
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_controller.hasClients) {
_controller.jumpTo(_controller.position.maxScrollExtent);
} else {
setState(() => null);
}
});
return PutYourListViewHere
}),
...
Run Code Online (Sandbox Code Playgroud)
我也尝试过,_controller.animateTo但似乎不起作用。
如果您想看到最后一个可见的项目,并且从底部开始有填充,那么添加额外的距离,如下所示
_controller.jumpTo(_controller.position.maxScrollExtent + 200);
Run Code Online (Sandbox Code Playgroud)
这里 200 是额外距离
| 归档时间: |
|
| 查看次数: |
25126 次 |
| 最近记录: |