CustomScrollView: Body scrolls under SliverAppBar

Issue

Flutter DartPad

I have multiple SliverAppBar‘s within a CustomScrollView, the body of the screen is within the SliverFillRemaining.


The Top SliverAppBar is pinned
The Middle SliverAppBar is an image and will collapse as the user scrolls
Bottom SliverAppBar is a TabBar that is pinned and will stay under the First SliverAppBar after the Image has fully collapsed

The current experience is that when you scroll initially, the body scrolls under the lowest SliverAppBar. I have already tried to use SliverOverlapAbsorber/Injector, but that just adds a space to the top of the body so that the spaces get overlapped rather than the body, but this is not what I want.


I want the body and the SliverAppBars to scroll together until the Middle SliverAppBar has collapsed completely, then I want the body to scroll.

I have been working on this for hours, How do you stop the body from being overlapped on scroll?

Solution

To achieve this kind of scrolling behaviour it’s easier to use NestedScrollView and note that the main appbar isn’t in the slivers anymore

enter image description here

import 'package:flutter/material.dart';

final Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  MyWidgetState createState() => MyWidgetState();
}

class MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin {
  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, length: 2);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('title'),
        elevation: 0,
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: () {},
        ),
      ),
      body: NestedScrollView(
        floatHeaderSlivers: true,
        physics: const BouncingScrollPhysics(),
        body: TabBarView(
          controller: _tabController,
          physics: const NeverScrollableScrollPhysics(),
          children: [
            SingleChildScrollView(
              physics: const NeverScrollableScrollPhysics(),
              child: Column(
                children: List.generate(
                  1000,
                  (index) => Text('Tab One: $index'),
                ),
              ),
            ),
            SingleChildScrollView(
              physics: const NeverScrollableScrollPhysics(),
              child: Column(
                  children: List.generate(
                1000,
                (index) => Text('Tab Two: $index'),
              )),
            )
          ],
        ),
        headerSliverBuilder: (context, innerBoxIsScrolled) {
          return <Widget>[
            SliverAppBar(
              pinned: true,
              floating: false,
              elevation: 0,
              toolbarHeight: 0,
              collapsedHeight: null,
              automaticallyImplyLeading: false,
              expandedHeight: MediaQuery.of(context).size.height * .4,
              flexibleSpace: const FlexibleSpaceBar(
                  collapseMode: CollapseMode.parallax,
                  background: Placeholder()),
              titleSpacing: 0,
              primary: false,
            ),
            SliverAppBar(
              pinned: true,
              forceElevated: true,
              primary: false,
              automaticallyImplyLeading: false,
              expandedHeight: 50,
              collapsedHeight: null,
              toolbarHeight: 50,
              titleSpacing: 0,
              title: Align(
                alignment: Alignment.topCenter,
                child: TabBar(
                    controller: _tabController,
                    isScrollable: true,
                    tabs: [
                      const Text('Tab One'),
                      const Text('Tab Two'),
                    ]),
              ),
            ),
          ];
        },
      ),
    );
  }
}

Answered By – Raouf Rahiche

Answer Checked By – Gilberto Lyons (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.