skip to Main Content

I have a CustomScrollView with a SliverAppBar and some slivers. I want to overlay some graphics on the SliverAppBar and have them scroll with the rest of the list.

Here’s the code I’ve been working with:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: SliverTest(),
    );
  }
}

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

  @override
  State<SliverTest> createState() => _SliverTestState();
}

class _SliverTestState extends State<SliverTest> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: CustomScrollView(
          slivers: [
            SliverAppBar(
              expandedHeight: 350.0,
              floating: false,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                background: Container(
                  color: Colors.yellow,
                  child: const Center(child: Text('Yellow')),
                ),
              ),
            ),
            SliverToBoxAdapter(
              child: Container(
                height: 100,
                color: Colors.blueAccent,
                child: Stack(
                  children: [
                    Align(
                      alignment: const Alignment(0.0, -2.0),
                      child: Container(
                        width: 50,
                        height: 50,
                        decoration: const BoxDecoration(
                          shape: BoxShape.circle,
                          color: Colors.red,
                        ),
                        child: const Center(child: Text('Red')),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Container(
                    height: 50,
                    color: Colors.teal[100 * ((index % 8) + 1)],
                    child: Center(child: Text('Item #$index')),
                  );
                },
                childCount: 20,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

In this setup, I want the red circle (which is currently clipped) to be drawn on top of the yellow section of the SliverAppBar. I’ve placed the red circle inside a sliver because I want it to scroll along with the rest of the list when the user interacts with the scrollable area.

Could anyone provide a simple example of how to achieve this in Flutter? I’m open to any other solutions that utilize slivers, as they provide huge convenience to the other parts of my real app. Otherwise, I’m aware that I may recreate it without utilizing the slivers. Any help would be greatly appreciated. Thanks in advance!

2

Answers


  1. This is the solution for your problem based on what I understood.

    class SliverTest extends StatefulWidget {
      const SliverTest({super.key});
    
      @override
      State<SliverTest> createState() => _SliverTestState();
    }
    
    class _SliverTestState extends State<SliverTest> {
      late ScrollController _scrollController;
    
      @override
      void initState() {
        _scrollController = ScrollController();
        super.initState();
      }
    
      @override
      void dispose() {
        _scrollController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: CustomScrollView(
              controller: _scrollController,
              shrinkWrap: true,
              slivers: [
                SliverAppBar.large(
                  pinned: true,
                  floating: false,
                  stretch: true,
                  automaticallyImplyLeading: false,
                  expandedHeight: 350.0,
                  backgroundColor: Colors.purple,
                  title: Center(
                    child: Container(
                      width: 50,
                      height: 50,
                      decoration: const BoxDecoration(
                        shape: BoxShape.circle,
                        color: Colors.red,
                      ),
                      child: const Center(child: Text('Red')),
                    ),
                  ),
                  flexibleSpace: FlexibleSpaceBar(
                    background: Container(
                      color: Colors.yellow,
                      child: const Center(child: Text('Yellow')),
                    ),
                  ),
                ),
                SliverToBoxAdapter(
                  child: Container(
                    // height: 100,
                    color: Colors.blueAccent,
                    child: Stack(
                      children: [
                        SingleChildScrollView(
                          child: Column(
                            children: [
                              for (int i = 0; i < 20; i++) ...[
                                Container(
                                  height: 50,
                                  color: Colors.teal[100 * ((i % 8) + 1)],
                                  child: Center(child: Text('Item #$i')),
                                )
                              ],
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                // SliverList(
                //   delegate: SliverChildBuilderDelegate(
                //     (BuildContext context, int index) {
                //       return Container(
                //         height: 50,
                //         color: Colors.teal[100 * ((index % 8) + 1)],
                //         child: Center(child: Text('Item #$index')),
                //       );
                //     },
                //     childCount: 20,
                //   ),
                // ),
              ],
            ),
          ),
        );
      }
    }
    
    Login or Signup to reply.
  2. You might consider using a Stack widget to overlay the graphic on top of the SliverAppBar. The challenge is, however, to make the graphic scroll along with the CustomScrollView. You can try and adjust the position of the graphic in response to the scroll offset of the CustomScrollView.

    Use a NotificationListener to listen to scroll events. Calculate the position of the graphic based on the scroll offset. Use a Stackand adjust the position of the graphic dynamically as the user scrolls.

    class _SliverTestState extends State<SliverTest> {
      double _offset = 0.0;
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: NotificationListener<ScrollNotification>(
              onNotification: (ScrollNotification scrollInfo) {
                setState(() {
                  _offset = scrollInfo.metrics.pixels;
                });
                return true;
              },
              child: Stack(
                children: [
                  CustomScrollView(
                    slivers: [
                      SliverAppBar(
                        expandedHeight: 350.0,
                        floating: false,
                        pinned: true,
                        flexibleSpace: FlexibleSpaceBar(
                          background: Container(
                            color: Colors.yellow,
                            child: const Center(child: Text('Yellow')),
                          ),
                        ),
                      ),
                      SliverToBoxAdapter(
                        child: Container(
                          height: 100,
                          color: Colors.blueAccent,
                        ),
                      ),
                      // other slivers
                    ],
                  ),
                  Positioned(
                    top: 350 - _offset, // Adjust position based on scroll offset
                    left: MediaQuery.of(context).size.width / 2 - 25,
                    child: Container(
                      width: 50,
                      height: 50,
                      decoration: const BoxDecoration(
                        shape: BoxShape.circle,
                        color: Colors.red,
                      ),
                      child: const Center(child: Text('Red')),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    

    You can see the NotificationListener listening to scroll events and updating the _offset variable. The Stack widget overlays the red circle over the CustomScrollView. And the Positioned widget adjusts the position of the red circle based on the scroll offset.

    See if that would place the red circle on top of the SliverAppBar and make it scroll with the rest of the list.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search