Search code examples
flutterflutter-layoutflutter-animation

Programmatically scrolling to the end of a ListView


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 programmatically scroll the ListView to the end. (e.g., something like a chat message list where new messages can be added at the end)

My guess is that I would need to create a ScrollController in my State object and pass it manually to the ListView constructor, so I can later call animateTo() / jumpTo() method on the controller. However, since I cannot easily determine the maximum scroll offset, it seems impossible to simply perform a scrollToEnd() type of operation (whereas I can easily pass 0.0 to make it scroll to the initial position).

Is there an easy way to achieve this?

Using reverse: true is not a perfect solution for me, because I would like the items to be aligned at the top when there are only a small number of items that fit within the ListView viewport.


Solution

  • 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),
              );
            }
          ),
        );
      }
    }