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

130 Views Asked by At

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,
          ),
        ),
      )
1

There are 1 best solutions below

0
Tim B On BEST ANSWER

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