How to get full size of a ScrollController
Asked Answered
P

3

44

I've attached a ScrollController to a CustomScrollView of [SliverAppBar, SliverList]

In a default case I would use reverse:true and animateTo (0.0) to move the scroll to the last element added but in this case using reverse would also reverse the SliverAppBar/SliverList order !

So I'd like to use animateTo ( sizeOfScrollableAfterElementAdded ) but I can't find this value.

Thank you !

Pyx answered 23/5, 2017 at 17:14 Comment(1)
I think maybe changing the title to How to scroll to end of a ScrollView programmatically will make this more reachable.Vichyssoise
S
109

You can use _scrollController.position.maxScrollExtent to scroll to the end. Make sure to do this in a post-frame callback so that it will include the new item you just added.

Here is an example of a sliver list that scrolls to the end as more items are added.

before after

import 'package:flutter/scheduler.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  State createState() => new MyHomePageState();
}


class MyHomePageState extends State<MyHomePage> {
  ScrollController _scrollController = new ScrollController();

  List<Widget> _items = new List.generate(40, (index) {
    return new Text("item $index");
  });

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      floatingActionButton: new FloatingActionButton(
        child: new Icon(Icons.arrow_downward),
        onPressed: () {
          setState(() {
            _items.add(new Text("item ${_items.length}"));
          });
          SchedulerBinding.instance.addPostFrameCallback((_) {
            _scrollController.animateTo(
              _scrollController.position.maxScrollExtent,
              duration: const Duration(milliseconds: 300),
              curve: Curves.easeOut,
            );
          });
        },
      ),
      body: new CustomScrollView(
        controller: _scrollController,
        slivers: [
          new SliverAppBar(
            title: new Text('Sliver App Bar'),
          ),
          new SliverList(
            delegate: new SliverChildBuilderDelegate(
              (context, index) => _items[index],
              childCount: _items.length,
            ),
          ),
        ],
      ),
    );
  }
}
Sackbut answered 23/5, 2017 at 18:17 Comment(10)
Thank you to let me discover this: SchedulerBinding.instance.addPostFrameCallback((_) {Kolb
This solution works perfectly, but what about achieving this in initState()? In a chat application for example, you would want to scroll to the bottom of the list on start. How can one achieve that?Abvolt
@Abvolt that's what the scheduler binding call is for. After the list is built and the controller is actually attached to it, then scroll to the bottom of it.Musty
@Musty the schedulerbinding in this example is on onPressed(), to Loolooii's question, how do you schedulebind on the initstate()?Julio
Right. You just put it in initstate. And it schedules it to be called when the current widget is finished building from what I understandMusty
For someone looking to scroll to the top, you can use this _scrollController.position.minScrollExtentNonmetal
Don't forget to dispose the controller: @override void dispose() {_scrollController.dispose();}Genu
@collin-jackson Thanks, it works like magic with Listview.builder in horizontal scrolling view.Ouzo
For me _scrollController.position.maxScrollExtent always returns 0.0. If I put a hard coded number it works.Duaneduarchy
The issue that _scrollController.position.maxScrollExtent always returns 0.0 was that my ListView was inside a ScrollView. Removing the ScrollView fixed the issue.Duaneduarchy
N
6

Also, if you would like to immediately scroll to the bottom of the list on start (in the initState()), you can place the following code chunk from Collin's solution before the first time you call setState().

class ExampleState extends State<Example>{
    void initState(){
    super.initState();

    _scrollController = new ScrollController();

    void _getListItems() async{

        //get list items

        SchedulerBinding.instance.addPostFrameCallback((_) {
          _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
        duration: const Duration(milliseconds: 100),
        curve: Curves.ease,
         );
        });

      setState((){
      });
   }
 }
}
Northamptonshire answered 12/7, 2018 at 23:58 Comment(3)
Can you have async items within initState()? I thought that was verboten? Also shouldn't there be a check for if(mounted) before the setState? But something about this looks a bit weird // but probably on the right trackMicropyle
From what it looks like, he simply defined a function there, but didn't call it. And yes, you can have async code, but you can't use await, because initState() can't be async. I don't think it makes sense to call setstate inside initstate because the widget hasn't been built yet, so it should initially build with the right valuesMusty
depending on your answer I have created this class to scroll to the end or the top of the listview https://mcmap.net/q/116133/-programmatically-scrolling-to-the-end-of-a-listviewRhinencephalon
R
1

I've modified the code before with jumpto() at the end of animation and the code is now :

setState(() {
        _items.add(Text("item ${_items.length}"));
      });

      SchedulerBinding.instance.addPostFrameCallback((_) async {            
        await _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeOut,
        );
        _scrollController
            .jumpTo(_scrollController.position.maxScrollExtent);
      });
Runofthemill answered 12/10, 2023 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.