Skip to content

Content behind AppBar when using FlexibleSpacebar #99908

@delizondo

Description

@delizondo

I'm trying to implement a collapsable AppBar, using a FlexibleSpaberBar and Tabs, the code "works" fine with the content of one of the tabs is a ListView, but, when the content is more static (inside a Column) and is smaller than the actual screen size, then when I scroll up in order to hide the FlexibleSpacebar, the content if the tab gets behind the app bar.

Attaching a working example to reproduce the problem.

code sample
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).primaryColor,
      body: DefaultTabController(
        length: 2,
        child: NestedScrollView(
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) {
              return [
                SliverOverlapAbsorber(
                  handle:
                      NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                  sliver: SliverAppBar(
                    elevation: 0.0,
                    title: const Text("App Bar Demo"),
                    expandedHeight: 200.0,
                    floating: true,
                    pinned: true,
                    forceElevated: innerBoxIsScrolled,
                    flexibleSpace: const FlexibleSpaceBar(
                      background: Placeholder(),
                    ),
                  ),
                ),
                SliverPersistentHeader(
                    pinned: true,
                    delegate: _SliverAppBarDelegate(TabBar(
                        unselectedLabelColor: Colors.grey[700],
                        indicatorSize: TabBarIndicatorSize.label,
                        tabs: const [
                          Tab(
                            child: Align(
                              alignment: Alignment.center,
                              child: Text("Tab 1"),
                            ),
                          ),
                          Tab(
                            child: Align(
                              alignment: Alignment.center,
                              child: Text("Tab 2"),
                            ),
                          ),
                        ])))
              ];
            },
            body: const TabBarView(
              children: [
                Tab1Widget(),
                Tab2Widet(),
              ],
            )),
      ),
    );
  }
}

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate(this._tabBar);

  final TabBar _tabBar;

  @override
  double get minExtent => _tabBar.preferredSize.height;
  @override
  double get maxExtent => _tabBar.preferredSize.height;

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      child: _tabBar,
    );
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return false;
  }
}

class Tab1Widget extends StatelessWidget {
  const Tab1Widget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Builder(builder: (BuildContext context) {
        return CustomScrollView(
          slivers: [
            SliverOverlapInjector(
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
            ),
            SliverSafeArea(
              sliver: SliverPadding(
                padding: const EdgeInsets.all(8.0),
                sliver: SliverToBoxAdapter(
                  child: Container(
                    color: Colors.green,
                    child: Column(
                      children: const [
                        Text(
                          "Content 1",
                          style: TextStyle(color: Colors.white),
                        ),
                        Text(
                          "Content 2",
                          style: TextStyle(color: Colors.white),
                        ),
                        Text(
                          "Content 3",
                          style: TextStyle(color: Colors.white),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            )
          ],
        );
      }),
    );
  }
}

class Tab2Widet extends StatelessWidget {
  const Tab2Widet({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

As you can see, the content of Tab1Widget is smaller than the screen size (although depending on the device actual screen size, it could be bigger than the screen size) and when I scroll up in order to hide the FlexibleSpaceBar, the content of Tab1Widget gets behind the AppBar

Expanded State Collapsed State
image image

I've also created a DartPad example here

Running the latest Flutter version in my machine

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.10.3, on macOS 12.1 21C52 darwin-x64, locale
    en-CR)
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.1)
[✓] VS Code (version 1.65.1)
[✓] Connected device (2 available)
[✓] HTTP Host Availability

Metadata

Metadata

Assignees

No one assigned

    Labels

    r: duplicateIssue is closed as a duplicate of an existing issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions