skip to Main Content
import 'package:flutter/material.dart';

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  var opacity = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.purple.shade100,
      body: SafeArea(
        child: Stack(
          children: [
            NotificationListener<ScrollUpdateNotification>(
              onNotification: (notification) {
                final pixels = notification.metrics.pixels / 100;

                setState(() {
                  if (pixels > 0.4) {
                    return;
                  }

                  opacity = pixels;
                });

                return true;
              },
              child: CustomScrollView(
                slivers: [
                  SliverAppBar(
                    expandedHeight: 0,
                    pinned: true,
                    backgroundColor: Colors.black.withOpacity(opacity),
                    shadowColor: Colors.transparent,
                    title: const Text(
                      'Stacked Container',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 22.0,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    toolbarHeight: 60,
                  ),
                  SliverAppBar(
                    clipBehavior: Clip.none,
                    floating: true,
                    toolbarHeight: 25,
                    shadowColor: Colors.transparent,
                    backgroundColor: Colors.transparent,
                    bottom: PreferredSize(
                      preferredSize: Size(double.infinity, 10),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          SizedBox(width: 15.0),
                          Opacity(
                            opacity: 1 - opacity, // Adjust the opacity based on scrolling
                            child: Text(
                              'Sliver AppBar',
                              style: TextStyle(
                                fontSize: 20.0,
                                color: Colors.green,
                              ),
                            ),
                          ),
                          SizedBox(width: 15.0),
                        ],
                      ),
                    ),
                  ),
                  SliverList(
                    delegate: SliverChildListDelegate(
                      [
                        ListView.builder(
                          itemCount: 10,
                          shrinkWrap: true,
                          physics: const NeverScrollableScrollPhysics(),
                          itemBuilder: (context, index) {
                            return Container(
                              margin:
                                  const EdgeInsets.fromLTRB(10.0, 20, 10, 10),
                              color: Colors.deepPurple,
                              height: 300,
                              width: double.infinity,
                            );
                          },
                        )
                      ],
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

I’m trying to recreate Netflix appbar behavior. In this code one SliverAppBar followed by another SliverAppBar. First one is pinned second one is not. I want the part of second SliverAppBar to disappear that gets under the first SliverAppBar.

Current
enter image description here

Desired:
The top half of text Sliver AppBar should disappear as it is under the first SliverAppBar.

2

Answers


  1. Chosen as BEST ANSWER
    import 'package:flutter/material.dart';
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatefulWidget {
      const MyApp({super.key});
    
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      late ScrollController _scrollController;
      bool isScrolledUp = false;
      double appBarHeight = 50.0;
      double _previousScrollPosition = 0.0;
    
      void _scrollListener() {
        final currentScrollPosition = _scrollController.position.pixels;
        final currentScrollVelocity = _scrollController.position.activity?.velocity;
    
        debugPrint(currentScrollVelocity.toString());
    
        if (currentScrollPosition < _previousScrollPosition) {
          debugPrint('Scrolling Up');
          setState(() {
            if (currentScrollPosition == 0) {
              appBarHeight = 50.0;
            }
            if (appBarHeight + 1 > 50) {
              appBarHeight = 50;
            } else {
              if (currentScrollVelocity != 0) {
                appBarHeight += 10;
              } else {
                appBarHeight += 1;
              }
            }
          });
        } else {
          debugPrint('Scrolling Down');
          setState(() {
            if (appBarHeight - 1 < 0) {
              appBarHeight = 0;
            } else {
              if (currentScrollVelocity != 0) {
                appBarHeight -= 10;
              } else {
                appBarHeight -= 0.6;
              }
            }
          });
        }
        _previousScrollPosition = currentScrollPosition;
      }
    
      @override
      void initState() {
        super.initState();
    
        _scrollController = ScrollController();
        _scrollController.addListener(_scrollListener);
      }
    
      @override
      void dispose() {
        _scrollController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: SafeArea(
              child: Stack(
                alignment: Alignment.topCenter,
                children: [
                  ListView.builder(
                    itemCount: 30,
                    shrinkWrap: true,
                    addRepaintBoundaries: true,
                    controller: _scrollController,
                    padding: const EdgeInsets.only(top: 100.0),
                    itemBuilder: (context, index) {
                      return Container(
                        margin: const EdgeInsets.only(bottom: 20.0),
                        color: Colors.deepPurple,
                        height: 200.0,
                      );
                    },
                  ),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      MyAppBar(
                        title: 'STATIC APPBAR',
                        color: Colors.black.withOpacity(0.5),
                        height: 50.0,
                      ),
                      MyAppBar(
                        title: 'DYNAMIC APPBAR 1',
                        color: Colors.red.withOpacity(0.5),
                        height: appBarHeight,
                      ),
                    ],
                  )
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class MyAppBar extends StatelessWidget {
      const MyAppBar({
        super.key,
        required this.title,
        required this.color,
        required this.height,
      });
      final String title;
      final Color color;
      final double height;
    
      @override
      Widget build(BuildContext context) {
        return SingleChildScrollView(
          child: AnimatedContainer(
            duration: Duration(milliseconds: 1),
            constraints: const BoxConstraints(maxHeight: 50.0, minHeight: 0.0),
            color: color,
            height: height,
            child: Text(
              title,
              style: const TextStyle(
                color: Colors.white,
                fontSize: 20.0,
                fontWeight: FontWeight.bold,
              ),
              textAlign: TextAlign.center,
            ),
          ),
        );
      }
    }
    

    After trying different ways to implement this is the closest that I could get to right now. Feel free to share if If it can be done more efficiently. With or without SliverAppBar!


  2. Changes made are:

    1, Listen to ScrollController.offset instead of ScrollUpdateNotification for layouts.

    2, Use SliverList.builder for building sliver children of CustomScrollView. So it can be rendered lazily.

    3, Keep opacity between 0 and 1. So the assertion doesn’t throw.

    4, Clip the content inside SliverAppBar instead of constraining the SliverAppBar. It’s easier to work with RenderBox widget comparing to RenderSliver widget.

    // ignore_for_file: prefer_const_constructors
    
    import 'package:flutter/material.dart';
    
    class HomePage extends StatefulWidget {
      const HomePage({super.key});
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      var opacity = 0.0;
      var clipHeight = 25.0;
      late final ScrollController _scrollController;
    
      @override
      void initState() {
        super.initState();
        _scrollController = ScrollController();
        _scrollController.addListener(() {
          setState(() {
            var appBarOffset = _scrollController.offset;
            if (appBarOffset > 100) {
              appBarOffset = 100;
            }
            if (appBarOffset < 0) {
              appBarOffset = 0;
            }
            opacity = appBarOffset / 100; // keep it between 0,1
            clipHeight = 25 - _scrollController.offset;
            if (clipHeight > 25) {
              clipHeight = 25;
            }
            if (clipHeight < 0) {
              clipHeight = 0;
            }
          });
        });
      }
    
      @override
      void dispose() {
        _scrollController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.purple.shade100,
          body: SafeArea(
            child: CustomScrollView(
              controller: _scrollController,
              slivers: [
                SliverAppBar(
                  expandedHeight: 0,
                  pinned: true,
                  backgroundColor: Colors.black.withOpacity(
                    opacity,
                  ),
                  shadowColor: Colors.transparent,
                  title: Text(
                    'Stacked Container',
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 22.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  toolbarHeight: 60,
                ),
                SliverAppBar(
                  clipBehavior: Clip.none,
                  floating: true,
                  toolbarHeight: 25,
                  shadowColor: Colors.transparent,
                  backgroundColor: Colors.transparent,
                  bottom: PreferredSize(
                    preferredSize: Size(double.infinity, 10),
                    child: SizedBox(
                      height: 25,
                      child: Column(
                        children: [
                          SizedBox(height: 25 - clipHeight),
                          Container(
                            height: clipHeight,
                            color: Colors.amber,
                            child: SingleChildScrollView(
                              reverse: true,
                              child: Row(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  SizedBox(width: 15.0),
                                  Opacity(
                                    opacity: 1 - opacity,
                                    // Adjust the opacity based on scrolling
                                    child: Text(
                                      'Sliver AppBar',
                                      style: TextStyle(
                                        fontSize: 20.0,
                                        color: Colors.green,
                                      ),
                                    ),
                                  ),
                                  SizedBox(width: 15.0),
                                ],
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
                SliverList.builder(
                  itemCount: 10,
                  itemBuilder: (context, index) {
                    return Container(
                      margin: const EdgeInsets.fromLTRB(10.0, 20, 10, 10),
                      color: Colors.deepPurple,
                      height: 300,
                      width: double.infinity,
                    );
                  },
                )
              ],
            ),
          ),
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search