1

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?

mrgnhnt96
  • 3,562
  • 1
  • 17
  • 36

1 Answers1

2

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'),
                    ]),
              ),
            ),
          ];
        },
      ),
    );
  }
}
Raouf Rahiche
  • 28,948
  • 10
  • 85
  • 77
  • Thank you! I am noticing that fling (swiping down fast and releasing to let scroll continue) doesn't seem to be working when I scroll down, it works fine when scrolling up. Do you know why that is? – mrgnhnt96 May 06 '21 at 14:45
  • I'm not sure I understand, when I swipe up/down fast the scroll continues normally to some extent based on the velocity – Raouf Rahiche May 06 '21 at 14:56
  • Its hard to tell on a computer, you'd need to use it on a physical device to really see it. The placeholder doesn't fling at all, it only scrolls when the finger is touching the screen – mrgnhnt96 May 06 '21 at 15:15
  • 1
    @mrgnhnt96 Got it, you can fix that by changing the physics of NestedScrollView to BouncingScrollPhysics check the updated example – Raouf Rahiche May 06 '21 at 15:29
  • I am not seeing a difference, its still doesn't fling – mrgnhnt96 May 06 '21 at 16:03
  • I can see the difference (I'm testing on iOS simulator btw) – Raouf Rahiche May 06 '21 at 17:51
  • 1
    I’m testing on a physical device. I’ll keep playing around with it – mrgnhnt96 May 07 '21 at 18:05