SliverAppBar not scrolling when nested ListView uses a ScrollController
Asked Answered
L

4

6

Minimum reproducible code:

final _controller = ScrollController();

@override
Widget build() {
  return NestedScrollView(
    headerSliverBuilder: (_, __) => [SliverAppBar(expandedHeight: 300)],
    body: ListView.builder(
      controller: _controller, // Removing this solves the issue. 
      itemCount: 100,
      itemBuilder: (_, i) => Text('$i'),
    ),
  );
}

If I scroll my ListView, the SliverAppBar doesn't scroll but if I remove the controller property then it does scroll.

So, how can I use the controller and make the SliverAppBar to scroll with the ListView (i.e. the standard behavior)?


Note: I don't want to use the CustomScrollView as my tree hierarchy won't let me make use of it that well.

Learn answered 25/5, 2022 at 18:13 Comment(3)
I have read the docs that I shouldn't provide a ScrollController to the nested ListView but the question is how can I make it work (even if docs say you shouldn't)Learn
What do you need to use the controller for?Ultra
@MichaelHorn I could have set the ScrollController to NestedScrollView but I want to scroll to the last position in the ListView, which NestedScrollView can't do.Learn
R
0

Yes you can. I don't like this solution but it works. Using GlobalKey

  void scrollToBottom() {
    final scrollController = globalKey.currentState!.innerController;

    scrollController.animateTo(
      scrollController.position.maxScrollExtent,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeOut,
    );
  }

Use GlobalKey

final globalKey = GlobalKey<NestedScrollViewState>();

NestedScrollView(
      key: globalKey, // add this
Recti answered 25/6 at 23:38 Comment(0)
B
-1

You should make sure that the child is wrapped

final _controller = ScrollController();

@override
Widget build() {
  return NestedScrollView(
    headerSliverBuilder: (_, __) => [SliverAppBar(expandedHeight: 300)],
    body: ListView.builder(
      //add this to the child ListView
      shrinkWrap: true
      controller: _controller, // Removing this solves the issue. 
      itemCount: 100,
      itemBuilder: (_, i) => Text('$i'),
    ),
  );
}
Bilious answered 25/5, 2022 at 19:51 Comment(3)
This makes it load all children immediately and thus defeats the purpose of using a ListView.Irairacund
@user1032613 Correct but even setting the shrinkWrap: true doesn't solve the issue.Learn
@Learn Correct but neither of us downvoted this post lol.Irairacund
D
-1

Just move the scroll controller to NestedScrollView like this:

Widget build() {
  return NestedScrollView(
    headerSliverBuilder: (_, __) => [SliverAppBar(expandedHeight: 300)],
    body: ListView.builder(
      //add this to the child ListView
      shrinkWrap: true,
      itemCount: 100,
      itemBuilder: (_, i) => Text('$i'),
    ),
    controller: _controller,
  );
}

That worked for me, I had a waterfalls flow using the scroll controller. and I need the sliver appear style.

Debris answered 18/9, 2022 at 19:7 Comment(1)
Thanks but I need the controller for my ListView, your answer defeats that purpose. What if I now do controller.jumpTo(200), it will also scroll the SliverAppBar and this is what I don't want.Learn
K
-1

Perhaps you don't need to use a controller, in my case I just wanted to do an infinite scroll with some posts and save the page position when changing Tab. If this is the case, maybe you are looking for something like this:

//State

class _TabOneState extends State<TabOne> {
  final ScrollController _scrollController = new ScrollController();

  @override
  Widget build(BuildContext context) {
    return Tab(
      child: RefreshIndicator(
        child: NotificationListener<ScrollNotification>(
          onNotification: (scrollNotification) {
            final maxScroll = scrollNotification.metrics.maxScrollExtent;
            final currentScroll = scrollNotification.metrics.pixels;

            //I use 65 as offset below
            if (maxScroll - currentScroll <= YOUR_OFFSET_HERE) {
              // fetch more data
            }
          },
          child: CustomScrollView(
            slivers: [
              SliverList(
                delegate: SliverChildListDelegate(
                  List<Widget>.generate(50, (i) {
                    return ListTile(
                      title: Text('${widget.items[i]}'),
                    );
                  }),
                ),
              )
            ],
          ),
          onRefresh: () {},
        ),
      ),
    );
  }
}

Code reached here on Github

And... if you need to store Scroll position, use PageStorageKey:

//...
child: CustomScrollView(
  key: const PageStorageKey<String>('UniqueKey'),
  slivers: [
    SliverList.builder(
//...
Kalman answered 13/4 at 14:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.