position. maxScrollExtent; This simply means we are asking for the last position in the ListView via ScrollController.
All you have to do is set Global Keys for your widgets and call Scrollable. ensureVisible on the key of your widget you want to scroll to. For this to work your ListView should be a finite List of objects. If you are using ListView.
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.
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),
);
While all the answers produces the desired effects we should do some improvements here.
First of all in most cases (speaking about auto scrolling) is useless using postFrameCallbacks because some stuff could be rendered after the ScrollController attachment (produced by the attach
method), the controller will scroll until the last position that he knows and that position could not be the latest in your view.
Using reverse:true
should be a good trick to 'tail' the content but the physic will be reversed so when you try to manually move the scrollbar you must move it to the opposite side -> BAD UX.
Using timers is a very bad practice when designing graphic interfaces -> timer are a kind of virus when used to update/spawn graphics artifacts.
Anyway speaking about the question the right way to accomplish the task is using the jumpTo
method with the hasClients method as a guard.
Whether any ScrollPosition objects have attached themselves to the ScrollController using the attach method. If this is false, then members that interact with the ScrollPosition, such as position, offset, animateTo, and jumpTo, must not be called
Speaking in code simply do something like this:
if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
Anyway this code is still not enough, the method will be triggered even when the scrollable isn't at the end of the screen so if you are manually moving the bar the method will triggered and autoscrolling will be performed.
We ca do better, with the help of a listener and a couple of bool will be fine.
I'm using this technique to visualize in a SelectableText the value of a CircularBuffer of size 100000 and the content keeps updating correctly, the autoscroll is very smooth and there are not performance issues even for very very very long contents. Maybe as someone said in other answers the animateTo
method could be smoother and more customizable so feel free to give a try.
ScrollController _scrollController = new ScrollController();
bool _firstAutoscrollExecuted = false;
bool _shouldAutoscroll = false;
void _scrollToBottom() {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
void _scrollListener() {
_firstAutoscrollExecuted = true;
if (_scrollController.hasClients && _scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
_shouldAutoscroll = true;
} else {
_shouldAutoscroll = false;
}
}
initState
:@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
}
dispose
:@override
void dispose() {
_scrollController.removeListener(_scrollListener);
super.dispose();
}
_scrollToBottom
, basing on your logic and needs, in your setState
:setState(() {
if (_scrollController.hasClients && _shouldAutoscroll) {
_scrollToBottom();
}
if (!_firstAutoscrollExecuted && _scrollController.hasClients) {
_scrollToBottom();
}
});
EXPLANATION
_scrollToBottom()
in order to avoid code repetitions;_scrollListener()
and we attached it to the _scrollController
in the initState
-> will be triggered after the first time that the scrollbar will move. In this listener we update the value of the bool value _shouldAutoscroll
in order to understand if the scrollbar is at the bottom of the screen.dispose
just to be sure to not do useless stuff after the widget dispose.setState
when we are sure that the _scrollController
is attached and that's at the bottom (checking for the value of shouldAutoscroll
) we can call _scrollToBottom()
._scrollToBottom()
short-circuiting on the value of _firstAutoscrollExecuted
.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With