Flutter - ListView.builder: Initial scroll position
Asked Answered
V

7

18

I want to setup the initial scroll position of a ListView.builder, I want the list to start at the bottom 0.0

If I setup reverse on the listView of course I get the initial scroll position to be the desired one, but what I need is to have the last children on the bottom, is a chat app.

This is the list builder, MessageItem() is the chat message

ListView.builder(
                    shrinkWrap: true,
                    controller: _scrollController,
                    itemCount: snapshot.data.documents.length,
                    padding: EdgeInsets.all(10.0),
                    itemBuilder: (BuildContext context, int index) =>
                        MessageItem(
                            index: index,
                            document: snapshot.data.documents[index],
                            myId: myId));

This is the animation I have when someone send the message


_scrollController.animateTo(
                _scrollController.position.maxScrollExtent,
                duration: const Duration(milliseconds: 500),
                curve: Curves.easeOut);

the animation works okay.


What I want is the list scroll position to be already at the bottom when the user enters the chat room.

Velarium answered 15/8, 2019 at 1:59 Comment(7)
Would using a duration of 0 milliseconds work?Upturn
why not set the intial position of the scroll controller in the initState of the widget. this way in the intial widget build it'll scroll to the bottom position.Teena
@Upturn Well, that is just an animation that happens when the user press the "send message" button, that works. I tried putting the animation code on initState() but it throws error: ``` I/flutter (15097): ScrollController not attached to any scroll views. I/flutter (15097): 'package:flutter/src/widgets/scroll_controller.dart': I/flutter (15097): Failed assertion: line 110 pos 12: '_positions.isNotEmpty' ``` Where should I put it so it can be triggered at the beginning?Velarium
@Salma. tried that, i get an error.Velarium
set it to 0.0 while initializing the scrollController >>> _scrollController = new ScrollController( initialScrollOffset: 0.0, keepScrollOffset: true, );Teena
@Salma. It doesn't seem to work, perhaps is because the ListView is a builder, ListView.builder , so that is why I need to setup the animation as: _scrollController.position.maxScrollExtent instead of 0.0 but of course this causes an error, it needs some mounting I guess.Velarium
just change the index to snapshot.data.length-index-1Apart
A
28

for avoid error where scroll isnt attached for any list, use, WidgetsBinding to pullout after build is ready.


void _scrollToBottom() {
    if (_scrollController.hasClients) {
      _scrollController.animateTo(_scrollController.position.maxScrollExtent,
          duration: Duration(milliseconds: 300), curve: Curves.elasticOut);
    } else {
      Timer(Duration(milliseconds: 400), () => _scrollToBottom());
    }
 }

@override
Widget build(BuildContext context) {

  WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom());

  {...} //rest of code;
}
Accept answered 3/6, 2020 at 14:49 Comment(1)
Not sure why this answer didn't get more love. This solved a problem I've been having with selecting checkboxes in a ListView and keeping the view static, instead of always jumping back to top. Thanks!Argilliferous
M
21

You can set one of ScrollController's property: initialScrollOffset

But on the condition that you know the offset position of the target item/index.

ScrollController _controller = ScrollController(initialScrollOffset: itemHeight * index)

(note that this example assumes that the sizes of your list's widgets are of constant size, but if you don't have constant widget sizes in your list, then you must be able to calculate the final offset position of your target item - and that is a whole other issue)

Or, if you want it to always be the last item/index, then it's much easier:

ScrollController _controller = ScrollController(initialScrollOffset: _controller.position.maxScrollExtent)
Misguided answered 11/11, 2019 at 23:20 Comment(5)
You can't use _controller while constructing itTomsk
@Tomsk for that there is a special method - addpostframecallback, it is fired right after the widget tree is built so you don't have to guess the delay in the initstate.Barrington
@Tomsk agree with you, but you could have a check something like this if(_controller.hasClients){ <Then Scroll to desired positon>}Adjustment
initialOffset will not animate to the positionNotwithstanding
This actually works perfectly, you gotta calculate the offset and pass it as parameter. I tested it and works perfectlyReremouse
E
8

The solution for me was:

SingleChildScrollView(
                reverse: true,
                child: Container(),
              ),

ListView has also reverse property.

Earleenearlene answered 14/5, 2020 at 7:32 Comment(1)
Also you should reverse your array message.Narayan
B
3

I feel your pain as it is an essential requirement because there could be tens of thousands of messages in a single chat.

To tackle that you can use

SchedulerBinding.instance.addPostFrameCallback((_){});

This callback is fired right after the widget tree is built, so you can just

scrollController.jump(scrollController.position.maxScrollExtent);

That however won't work if you have messages appear asynchronously, that is, after the initstate with some function that pulls it off from firestore document for instance. In this case you will first need for them to load, and only then do the steps above. Hope this helps.

Barrington answered 10/6, 2020 at 11:42 Comment(0)
A
0

There is something simple assume that you are getting a list from API in future builder and returning the listview.builder

snapshot.data.length-index-1

remove the reverse property from the listview.builder

Apart answered 27/9, 2021 at 6:4 Comment(0)
R
0

You can do something like this to create a listview the first time scroll to the bottom

save this code in initState

// From here
await Future.delayed(
      const Duration(milliseconds: 500),
      () => scrollToBottom(),
    );
// end

void scrollToBottom() {
    scrollController.animateTo(
      scrollController.position.maxScrollExtent + 73,
      duration: const Duration(milliseconds: 500),
      curve: Curves.ease,
    );
  }
Reaves answered 4/6 at 1:42 Comment(0)
C
-2

You can also simply use the FixedExtentScrollController for same size items with the index of your initialItem :

controller: FixedExtentScrollController(initialItem: itemIndex);

The documentation : Creates a scroll controller for scrollables whose items have the same size.

Cheeseburger answered 17/2, 2022 at 23:1 Comment(1)
At the moment "FixedExtentScrollController can only be used with ListWheelScrollViews". Ref: github.com/flutter/flutter/issues/28012Multiplication

© 2022 - 2024 — McMap. All rights reserved.