我有一个可滚动的ListView,其中项目的数量可以动态变化。每当一个新项目被添加到列表的末尾时,我希望以编程的方式将ListView滚动到末尾。(例如,类似聊天消息列表的东西,可以在最后添加新消息)
我的猜测是,我需要在我的State对象中创建一个ScrollController,并手动将其传递给ListView构造函数,这样我就可以稍后在控制器上调用animateTo() / jumpTo()方法。然而,由于我不容易确定最大滚动偏移量,因此似乎不可能简单地执行scrollToEnd()类型的操作(而我可以轻松地传递0.0使其滚动到初始位置)。
有没有简单的方法来实现这个目标?
使用reverse: true对我来说不是一个完美的解决方案,因为当只有少量的项目适合ListView视口时,我希望项目在顶部对齐。
我在使用StreamBuilder小部件从数据库中获取数据时遇到了这个问题。我把WidgetsBinding.instance.addPostFrameCallback放在小部件的构建方法的顶部,它不会一直滚动到最后。我是这样解决的:
...
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
}),
...
我尝试了_controller。animateTo太,但它似乎没有工作。
如果你使用一个带有reverse: true的收缩包装的ListView,那么将它滚动到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),
);
}
),
);
}
}
截图:
Scrolling with animation:
final ScrollController _controller = ScrollController();
// This is what you're looking for!
void _scrollDown() {
_controller.animateTo(
_controller.position.maxScrollExtent,
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton.small(
onPressed: _scrollDown,
child: Icon(Icons.arrow_downward),
),
body: ListView.builder(
controller: _controller,
itemCount: 21,
itemBuilder: (_, i) => ListTile(title: Text('Item $i')),
),
);
}
Scrolling without animation:
Replace above _scrollDown method with this:
void _scrollDown() {
_controller.jumpTo(_controller.position.maxScrollExtent);
}
我在使用StreamBuilder小部件从数据库中获取数据时遇到了这个问题。我把WidgetsBinding.instance.addPostFrameCallback放在小部件的构建方法的顶部,它不会一直滚动到最后。我是这样解决的:
...
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
}),
...
我尝试了_controller。animateTo太,但它似乎没有工作。