Search code examples
androidflutternestedscrollview

Using nestedscrollview with bottomnavigationbar and children/pages to scroll of nestedscrollview


Basically i have a shellroute builder (go router) where i want to display the appbar and bottomnavigationbar on all child routes.

    ShellRoute(
          navigatorKey: _shellNavigatorKey,
          builder: (context, state, child) => DashboardScreen(child: child),

The issue I'm facing is how to make all child routes which are widget.child on DashboardScreen scrolling hide/show/float the sliverappbar?!

class DashboardScreen extends ConsumerStatefulWidget {
  const DashboardScreen({Key? key, required this.child}) : super(key: key);
  final Widget child;
...
    DefaultTabController(
        length: 6,
        child: Scaffold(
          bottomNavigationBar: bottomMenuBuilder(context),
          padding: EdgeInsets.zero,
          body: NestedScrollView(
            floatHeaderSlivers: true,
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) {
              return <Widget>[
                const SliverAppBar(
                  title: Text("floating appbar"),
                  centerTitle: false,
                  automaticallyImplyLeading: false,
                  floating: true,
                  actions: [
                    Icon(Icons.search, size: 30, color: Colors.white),
                  ],
                  elevation: 0.0,
                ),
              ];
            },
            body: widget.child,
          ),
        ),
      )

Solution

  • I was facing the same issue and came up with a (dirty) workaround. I simply disable user scrolling for the NestedScrollView and all child scroll views. Then I stack a custom scrollview on top of my ShellRoute and listen for the scroll notification of this scroll view. I use the offset to calculate the offets for NestedScrollView and the child scroll views.

    Here is my code:

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    import 'package:go_router/go_router.dart';
    import 'package:provider/provider.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp.router(
          routerConfig: _router,
          title: 'Flutter Demo',
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: true,
          ),
        );
      }
    }
    
    // GoRouter configuration
    final _router = GoRouter(
      initialLocation: "/home",
      routes: [
        ShellRoute(
          builder: (context, state, child) {
            return ShellRoutePage(child: child);
          },
          routes: [
            GoRoute(
              path: "/home",
              builder: (context, state) {
                return MyHomePage();
              },
            ),
          ],
        ),
      ],
    );
    
    class ShellRoutePage extends StatelessWidget {
      final Widget child;
    
      const ShellRoutePage({super.key, required this.child});
    
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider<ShellRoutePageState>(
          create: (_) => ShellRoutePageState(),
          child: Builder(builder: (context) {
            return Stack(
              children: [
                NestedScrollView(
                  controller: context.read<ShellRoutePageState>().topController,
                  physics: const NeverScrollableScrollPhysics(),
                  headerSliverBuilder: (context, innerBoxIsScrolled) {
                    return [
                      const SliverAppBar(
                        pinned: true,
                        expandedHeight: 200,
                        flexibleSpace: FlexibleSpaceBar(
                          background: ColoredBox(
                            color: Colors.yellow,
                            child: Center(
                                child: Text(
                                    "go_router with nested scroll view and custom scrollview on top of shell route")),
                          ),
                        ),
                      )
                    ];
                  },
                  body: child,
                ),
                NotificationListener<ScrollNotification>(
                  onNotification: (notification) {
                    context
                        .read<ShellRoutePageState>()
                        .onScroll(notification.metrics.extentBefore);
                    print(notification.metrics.extentBefore);
                    return true;
                  },
                  child: ListView.builder(
                    itemBuilder: (context, index) {
                      return Text("$index");
                    },
                  ),
                ),
              ],
            );
          }),
        );
      }
    }
    
    class ShellRoutePageState with ChangeNotifier {
      ScrollController topController = ScrollController();
      ScrollController bottomController = ScrollController();
    
      void onScroll(double offset) {
        double topMax = topController.position.maxScrollExtent;
        double topOffset = min(topMax, offset);
        topController.jumpTo(topOffset);
    
        double bottomOffset = max(0, offset - topMax);
        bottomController.jumpTo(bottomOffset);
      }
    }
    
    class MyHomePage extends StatelessWidget {
      final List<Color> _colors = [Colors.red, Colors.blue, Colors.green];
    
      @override
      Widget build(BuildContext context) {
        return CustomScrollView(
          controller: context.read<ShellRoutePageState>().bottomController,
          physics: const NeverScrollableScrollPhysics(),
          slivers: [
            SliverList.builder(
              itemBuilder: (context, index) {
                return Container(
                  height: 100,
                  color: _colors[index % _colors.length],
                );
              },
            )
          ],
        );
      }
    }
    

    EDIT I filed an issue: https://github.com/flutter/flutter/issues/138209