3

Expected behaviour: The nested scroll view scrolls normaly if the custom scroll view gets scrolled and thus scrolls the appbar with it.

Actually happening: Only the custom scrollview scrolls, but not the appbar (=nested scrollview).

Wanted Behaviour can be achieved by either not using a customscrollview or not using a navigator (both no option in my case). This implementation worked before null safety if it helps in any case.

Code example:

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

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: TestScreen(),
    );
  }
}

class TestScreen extends StatefulWidget {
  @override
  _TestScreenState createState() => _TestScreenState();
}

class _TestScreenState extends State<TestScreen> {
  GlobalKey<NavigatorState> get navigatorKey =>
      GlobalKey<NavigatorState>();


  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: NestedScrollView(
          physics: BouncingScrollPhysics(),
          headerSliverBuilder: (context, innerBoxIsScrolled) => [
            SliverOverlapAbsorber(
                handle:
                    NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                sliver: SliverAppBar(
                  title: Text("title"),
                  pinned: true,
                  floating: false,
                  snap: false,
                  expandedHeight: 200,
                ))
          ],
          // body: DemoPage(),
          body: Navigator(
              key: navigatorKey,
              onGenerateRoute: (settings) =>
                  MaterialPageRoute(builder: (context) => DemoPage())),
        ));
  }
}

class DemoPage extends StatelessWidget {

  Color generateRandomColor1() {
    // Define all colors you want here
    const predefinedColors = [
      Colors.red,
      Colors.green,
      Colors.blue,
      Colors.black,
      Colors.white
    ];
    Random random = Random();
    return predefinedColors[random.nextInt(predefinedColors.length)];
  }

  @override
  Widget build(BuildContext context) {
    // return Container(
    //   color: Colors.red,
    //   height: 1000,
    //   width: 500,
    //   child: Center(
    //     child: RaisedButton(
    //       child: Text("Press"),
    //       onPressed: () {
    //         Navigator.of(context)
    //             .push(MaterialPageRoute(builder: (context) => DemoPage()));
    //       },
    //     ),
    //   ),
    // );
    return CustomScrollView(slivers: [
      SliverToBoxAdapter(
        child: Container(
          color: generateRandomColor1(),
          height: 1000,
          width: 500,
          child: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                RaisedButton(
                  child: Text("Press"),
                  onPressed: () {
                    Navigator.of(context)
                        .push(MaterialPageRoute(builder: (context) => DemoPage()));
                  },
                ),
                RaisedButton(
                  child: Text("pop"),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
              ],
            ),
          ),
        ),
      )
    ]);
  }
}
Ares
  • 41
  • 2

4 Answers4

0

Probably, your NestedScrollView acts as if there is an individual scroll view when DemoPage builds the CustomScrollView below the Navigator widget.

You can set the physics of the CustomScrollView to NeverScrollableScrollPhysics, so that the NestedScrollView can control it.

...

return CustomScrollView(
  // Add the following line:
  physics: NeverScrollableScrollPhysics(),
  slivers: [
    SliverToBoxAdapter(
      child: Container(
        color: generateRandomColor1(),
        height: 1000,
        width: 500,
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              RaisedButton(
                child: Text("Press"),
                onPressed: () {
                  Navigator.of(context).push(
                    MaterialPageRoute(
                      builder: (context) => DemoPage(),
                    ),
                  );
                },
              ),
              RaisedButton(
                child: Text("pop"),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          ),
        ),
      ),
    ),
  ],
);

...

PageView

If making CustomScrollView never scrollable doesn't give the desired result, you can try PageView instead of Navigator. Since you want to keep SliverAppBar stationary across all pages, this will provide navigation only inside PageView.

All you need to do is to put the list of your pages in PageView and pass its controller to your pages. Then, you can navigate to other pages via a button or swipe gesture.

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

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: TestScreen(),
    );
  }
}

class TestScreen extends StatefulWidget {
  @override
  _TestScreenState createState() => _TestScreenState();
}

class _TestScreenState extends State<TestScreen> {
  GlobalKey<NavigatorState> get navigatorKey => GlobalKey<NavigatorState>();

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    // Page controller
    final PageController controller = PageController(initialPage: 0);

    return Scaffold(
      body: NestedScrollView(
        physics: BouncingScrollPhysics(),
        headerSliverBuilder: (context, innerBoxIsScrolled) => [
          SliverOverlapAbsorber(
            handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
            sliver: SliverAppBar(
              title: Text("title"),
              pinned: true,
              floating: false,
              snap: false,
              expandedHeight: 200,
            ),
          )
        ],
        // Page view here
        body: PageView(
          // Scroll horizontally
          scrollDirection: Axis.horizontal,
          controller: controller,
          // Your pages
          children: <Widget>[
            // Pass the controller to your page
            DemoPage(pageController: controller),
            DemoPage(pageController: controller),
            DemoPage(pageController: controller),
          ],
        ),
      ),
    );
  }
}

class DemoPage extends StatelessWidget {
  // Page Controller
  final PageController pageController;

  DemoPage({required this.pageController});

  Color generateRandomColor1() {
    // Define all colors you want here
    const predefinedColors = [
      Colors.red,
      Colors.green,
      Colors.blue,
      Colors.black,
      Colors.white
    ];

    Random random = Random();

    return predefinedColors[random.nextInt(predefinedColors.length)];
  }

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverToBoxAdapter(
          child: Container(
            color: generateRandomColor1(),
            height: 1000,
            width: 500,
            child: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  RaisedButton(
                    child: Text("Press"),
                    onPressed: () {
                      // Go to next page
                      pageController.nextPage(
                        duration: Duration(milliseconds: 300),
                        curve: Curves.linear,
                      );
                    },
                  ),
                  RaisedButton(
                    child: Text("pop"),
                    onPressed: () {
                      // Go back to previous page
                      pageController.previousPage(
                        duration: Duration(milliseconds: 300),
                        curve: Curves.linear,
                      );
                    },
                  ),
                ],
              ),
            ),
          ),
        )
      ],
    );
  }
}
Stewie Griffin
  • 4,690
  • 23
  • 42
  • This might be possible but if I'd wanted to do this I could do this with a simpler solution such as a `Column` in a `SingleChildScrollView` . But I want to take advantage of the slivers such as `StickyHeader`s or some other stuffs. – Hossein Hadi Jun 23 '21 at 05:57
  • Did you try PageView? Check out the edited answer. – Stewie Griffin Jun 24 '21 at 11:34
  • The `PageView` might works and not worry about that. But I want to take full advantage of routing in the app like deep linking in the web and similar. – Hossein Hadi Jun 24 '21 at 15:25
0

It should work I guess. Also have a look at this post ref: nested_reference

headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
      return <Widget>[
        new SliverAppBar(
          pinned: true,
          floating: true,
          forceElevated: innerBoxIsScrolled,
.....
Dharman
  • 30,962
  • 25
  • 85
  • 135
Muhtar
  • 1,506
  • 1
  • 8
  • 33
0

You can find the answer here: Answer from github

There are 2 solutions: First, get innerController of NestedScrollView and pass it by the PrimaryScrollController widget below Navigator Second, access the state of NestedScrollView from your widget where you need a controller of NestedScrollView, get innerController of NestedScrollViewState and assign that innerController to your scrollable widget P.s. You can pass controller by InheritedWidget First-way example:

...

  GlobalKey<NavigatorState> get navigatorKey =>
      GlobalKey<NavigatorState>();


  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: NestedScrollView(
          physics: BouncingScrollPhysics(),
          headerSliverBuilder: (context, innerBoxIsScrolled) => [
            SliverOverlapAbsorber(
                handle:
                    NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                sliver: SliverAppBar(
                  title: Text("title"),
                  pinned: true,
                  floating: false,
                  snap: false,
                  expandedHeight: 200,
                ))
          ],
          // body: DemoPage(),
          body: Builder(builder: (context) {
                var scrollController = PrimaryScrollController.of(context);
return Navigator(
              key: navigatorKey,
              onGenerateRoute: (settings) =>
                  MaterialPageRoute(builder: (context) => PrimaryScrollController(controller: scrollController!,
                     child: Scaffold(body:ListView(
                            children:[
                            /// children
                            ],
                             ),
                               ),
                  ))),
        );
}
        );
  }
}

The second way: You can innerController of NestedScrollView

context.findAncestorStateOfType<NestedScrollViewState>()?.innerController then pass it to your scrollable widget

CustomScrollView(
      controller: context.findAncestorStateOfType<NestedScrollViewState>()?.innerController,
      ...
      )

I think that Navigator does not pass innerController of NestedView

Islomkhuja Akhrarov
  • 1,277
  • 9
  • 19
-1

Try removing pinned property of SliverAppBar.

Flutter documentation has interactive example of what it does.

https://api.flutter.dev/flutter/material/SliverAppBar-class.html

p2kr
  • 606
  • 6
  • 18