9

I am trying to make a list that is basically a log screen. That said, I need the list to keep scrolling to bottom all the time.

How this can be done?

Hasen
  • 11,710
  • 23
  • 77
  • 135
Daniel Oliveira
  • 8,053
  • 11
  • 40
  • 76

3 Answers3

26

Set reverse: true on the ListView widget and reverse the children list.

ListView(
  // Start scrolled to the bottom by default and stay there.
  reverse: true,
  children: widget.children.reversed.toList(growable: false),
),

If you have a very long list and reversing is expensive, you can reverse the index rather than the whole list using ListView.builder

ListView.builder(
    reverse: true,
    itemCount: items.length,
    itemBuilder: (context, index) {
      final reversedIndex = items.length - 1 - index;
      final item = items[reversedIndex];
      return MyWidget(item);
    }
)

I got this from Günter's comment above. Thanks!

Venkat D.
  • 2,979
  • 35
  • 42
M. Leonhard
  • 1,332
  • 1
  • 18
  • 20
  • 2
    Added an edit on how you can use a `ListView.builder` to accomplish the same – Venkat D. Dec 09 '20 at 14:47
  • Thanks a ton! For me the list was very long and my list was kind of a sticky list with an header where I had used a map to get the header from the key and the list items from the values (which are a list). This solution worked best for me. FYI, SchedulerBinding.instance.addPostFrameCallback((_) { controller1.animateTo( controller1.position.maxScrollExtent, duration: const Duration(milliseconds: 10), curve: Curves.easeOut,); }); } didn't work for me. It just scrolled my list halfway – Priya Sindkar Feb 18 '21 at 10:02
  • This will reverse the order of the items in the list, putting the last item first and the first item last. It will not actually show the bottom. It will show the top while switching the items around. – jwehrle Mar 01 '21 at 22:28
11

I found WidgetsBinding.instance.addPostFrameCallback extremely useful, especially in situations like this where you need some post build processing. In this case it solved the issue as follows:

  1. Define the ScrollController
final ScrollController _sc = ScrollController();
  1. In the ListView builder add the statement:
WidgetsBinding.instance.addPostFrameCallback((_) => {_sc.jumpTo(_sc.position.maxScrollExtent)});
  1. Add the controller to ListView parameters:
controller: _sc,

I was also building a logger viewer. Works like a charm.

mikyll98
  • 1,195
  • 3
  • 8
  • 29
Steve Pritchard
  • 207
  • 3
  • 5
9

I could make this work by using a Timer but probably should have a better way to do this. My workaround was:

  1. Define a ScrollController() and attach it to the listView:
ListView.builder(
      controller: _scrollController,
      itemCount: _logLines.values.length,
      itemBuilder: (context, index) => _getLogLine(index),
    )
  1. Override your page initState method and set a Timer inside it like:
 @override
  void initState() {
      super.initState();
      Timer.periodic(Duration(milliseconds: 100), (timer) {
        if (mounted) {
            _scrollToBottom();
        } else {
          timer.cancel();
        }
      });
    }
  1. Define a method _scrollToBottom() that calls:

    _scrollController.jumpTo(_scrollController.position.maxScrollExtent);

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Daniel Oliveira
  • 8,053
  • 11
  • 40
  • 76
  • Why you need a timer ? you can use .jumpTo only when there is a new log .. – Saed Nabil Nov 26 '18 at 15:36
  • 1
    My position object is always null, I have attached it to a singleChildScorllview. Can you help me understand why this is the case? – Gaurav Sobti Sep 13 '19 at 08:00
  • The answer for M. Leonhard is cleaner. You can avoid dealing with stateful widgets, timers, remembering to cancel them, and so on. – Venkat D. Dec 09 '20 at 14:49