Search code examples
flutterflutter-cupertino

Flutter - ScrollController with CupertinoSliverNavigationBar, largeTitle to smallTitle not working


When I use a ScrollController in a ListView, it blocks the CupertinoSliverNavigationBar largeTitle from transitioning to a smallTitle. However, if I remove the scrollController, the problem goes away. I think it might be a bug in the Cupertino Library

This code demonstrates the issue:

 ScrollController scrollController = ScrollController();

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            CupertinoSliverNavigationBar(
              largeTitle: Text('Large Title'),
            ),
          ];
        },
        body: ListView.builder(
            controller: scrollController,
            itemCount: 50,
            itemBuilder: (BuildContext context, int index) {
              return Container(
                height: 50,
                child: Center(child: Text('Entry ${index}')),
              );
            }),
      ),
    );
  }

Now if I remove the scrollController, the problem is gone:

ScrollController scrollController = ScrollController();

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            CupertinoSliverNavigationBar(
              largeTitle: Text('Large Title'),
            ),
          ];
        },
        body: ListView.builder(
            //controller: scrollController,
            itemCount: 50,
            itemBuilder: (BuildContext context, int index) {
              return Container(
                height: 50,
                child: Center(child: Text('Entry ${index}')),
              );
            }),
      ),
    );
  }

Solution

  • This is an expected behavior since NestedScrollView is meant to manage the scroll positions of all other child scrolling views:

    NestedScrollView class

    A scrolling view inside of which can be nested other scrolling views, with their scroll positions being intrinsically linked.

    So, you cannot control the positions of its children views with an individual ScrollController. However, you can provide one for NestedScrollView to manage all at once.

    NotificationListener

    There is still a way to listen to the scroll events of the inner scroll view besides using ScrollController. You can have your ListView inside of NotificationListener and check the scroll info that it provides.

    @override
    Widget build(BuildContext context) {
      return CupertinoPageScaffold(
        child: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return <Widget>[
              CupertinoSliverNavigationBar(
                largeTitle: Text('Large Title'),
              ),
            ];
          },
          body: NotificationListener<ScrollNotification>(
            onNotification: (ScrollNotification scrollInfo) {
              if (scrollInfo.metrics.pixels ==
                  scrollInfo.metrics.maxScrollExtent) {
                print('Reached the bottom');
              }
              return;
            },
            child: ListView.builder(
              itemCount: 50,
              itemBuilder: (BuildContext context, int index) {
                return Container(
                  height: 50,
                  child: Center(child: Material(child: Text('Entry $index'))),
                );
              },
            ),
          ),
        ),
      );
    }