RonanV
RonanV

Reputation: 13

How to Pin a SliverAppBar Below the Status Bar?

I have a layout with three Slivers:

  1. A SliverAppBar (not pinned).
  2. A second SliverAppBar (pinned, containing a SearchBar).
  3. A SliverList.

The issue is with the second SliverAppBar. I want it to be pinned below the status bar, respecting the safe area, but currently, it goes under the status bar, as shown in the second image:

reality vs expectation sliverappbar

The first drawing represents the default state on launch. When scrolling, the first SliverAppBar disappears as expected, and the second SliverAppBar (with the SearchBar) gets pinned at the top. However, I want the second SliverAppBar to stop just below the status bar instead of going behind it.

What I've Tried:

  1. Wrapping the entire CustomScrollView in a SafeArea:

    • This also shifts my first SliverAppBar, which I don't want.
  2. Wrapping only the second SliverAppBar in a SafeArea or SliverSafeArea:

    • This adds padding between the two SliverAppBars, which is not the desired behavior.
  3. Dynamic padding using a SliverPersistentHeader and working with its shrinkOffset:

    • However, shrinkOffset starts growing only when the header reaches the top of the screen, so it doesn't solve the problem.
  4. Using only 1 SliverAppBar with a flexibleSpace and a bottom:

    • This is close to the result I need but I didn't manage to get the proper sizes.

Sample code

class MyPage extends StatefulWidget {
  const MyPage({super.key});

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  late ScrollController _scrollController;

  @override
  void initState() {
    _scrollController = ScrollController();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PrimaryScrollController(
        controller: _scrollController,
        child: CustomScrollView(
          slivers: <Widget>[
            SliverAppBar(
              pinned: false,
              expandedHeight: 200,
              flexibleSpace: FlexibleSpaceBar(
                background: Image.network(
                  'https://picsum.photos/250?image=9',
                  fit: BoxFit.cover,
                ),
              ),
            ),
            const SliverAppBar(
              pinned: true,
              flexibleSpace: Padding(
                padding: EdgeInsets.only(top: 12.0),
                child: SearchBar(),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (context, index) {
                  return ListTile(
                    title: Text('Item $index'),
                  );
                },
                childCount: 20,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

How can I achieve this behavior? Is there a proper way to pin a SliverAppBar while respecting the safe area for just that Sliver?

Upvotes: 0

Views: 75

Answers (1)

SHANG
SHANG

Reputation: 74

Let both the shrinkage area and the search bar be implemented with SliverPersistentHeader:

enter image description here enter image description here

Widget buildPage1() {
    MediaQueryData mediaQuery = MediaQuery.of(context);
    var min = mediaQuery.viewPadding.top;

    final max = 200.0;

    return Scaffold(
      body: CustomScrollView(
        slivers: [
          NSliverPersistentHeaderBuilder(
            pinned: true,
            min: min,
            max: max,
            builder: (BuildContext context, double shrinkOffset, bool overlapsContent) {
              final double opacity = 1 - (shrinkOffset / (max - min));

              return Container(
                alignment: Alignment.bottomCenter,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  image: DecorationImage(
                    opacity: opacity,
                    image: ExtendedNetworkImageProvider(
                      'https://picsum.photos/250?image=9',
                    ),
                    fit: BoxFit.cover,
                  ),
                ),
                // child: Text(
                //   "Pinned Header ${{
                //     "shrinkOffset": shrinkOffset.toStringAsFixed(2),
                //   }} ",
                //   style: TextStyle(color: Colors.white, fontSize: 20),
                // ),
                // child: SearchBar(
                //   hintText: "search",
                // ),
              );
            },
          ),
          NSliverPersistentHeaderBuilder(
            pinned: true,
            min: 40,
            max: 40,
            builder: (BuildContext context, double shrinkOffset, bool overlapsContent) {
              return Container(
                padding: EdgeInsets.symmetric(horizontal: 12),
                decoration: BoxDecoration(
                  color: Colors.green,
                ),
                child: SearchBar(
                  backgroundColor: MaterialStateProperty.all(Colors.white),
                  hintText: "search",
                ),
              );
            },
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(
                  title: Text('Item #$index'),
                );
              },
              childCount: 20,
            ),
          ),
        ],
      ),
    );
   }

Upvotes: 0

Related Questions