I have a scrollable ListView
where the number of items can change dynamically. Whenever a new item is added to the end of the list, I would like to programmatic
Use ScrollController.jumpTo()
or ScrollController.animateTo()
method to achieve this.
Example:
final _controller = ScrollController();
@override
Widget build(BuildContext context) {
// After 1 second, it takes you to the bottom of the ListView
Timer(
Duration(seconds: 1),
() => _controller.jumpTo(_controller.position.maxScrollExtent),
);
return ListView.builder(
controller: _controller,
itemCount: 50,
itemBuilder: (_, __) => ListTile(title: Text('ListTile')),
);
}
If you want smooth scrolling, then instead of using jumpTo
above use
_controller.animateTo(
_controller.position.maxScrollExtent,
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
);
If you use a shrink-wrapped ListView
with reverse: true
, scrolling it to 0.0 will do what you want.
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),
);
}
),
);
}
}
listViewScrollController.animateTo(listViewScrollController.position.maxScrollExtent)
is the simplest way.
Do not put the widgetBinding in the initstate, instead, you need to put it in the method that fetches your data from database. for example, like this. If put in initstate, the scrollcontroller
will not attach to any listview.
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');
}
}
you could use this where 0.09*height
is the height of a row in the list and _controller
is defined like this _controller = ScrollController();
(BuildContext context, int pos) {
if(pos != 0) {
_controller.animateTo(0.09 * height * (pos - 1),
curve: Curves.easeInOut,
duration: Duration(milliseconds: 1400));
}
}
to get the perfect results I combined Colin Jackson and CopsOnRoad's answers as follows:
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 500),
);