shiiboun
shiiboun

Reputation: 63

Hide top header until scroll to certain height

I'm new to flutter and I want to implement something like this: (klook app)

Klook scroll

It's basically a button being shown when the user scrolls a bit.

I tried different things with a SliverAppBar and SliverStickyHeader, but I can't make it work like this. I also played with Opacity and Visibility but it moves my hole view and does not 'overlap' my banner/searchby widget.

My code so far:

class _ExplorePageState extends State<ExplorePage> {
  ScrollController _scrollController;
  bool lastStatus = true;

  _scrollListener() {
    if (isShrink != lastStatus) {
      print("listen");
      setState(() {
        lastStatus = isShrink;
      });
    }
  }

  bool get isShrink {
    return _scrollController.hasClients &&
        _scrollController.offset > (400 - kToolbarHeight);
  }

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

  @override
  void dispose() {
    _scrollController.removeListener(_scrollListener());
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        controller: _scrollController,
        slivers: <Widget>[
          SliverStickyHeader(
            header: Visibility(
              child: Container(
                color: Colors.red,
                height: isShrink ? 100 : 0,
                child: Text('Header 1'),
              ),
              visible: isShrink ? true : false,
              maintainState: true,
              maintainSize: true,
              maintainAnimation: true,
            ),
            sliver: SliverList(
              delegate: SliverChildListDelegate(
                [
                  BannerWidget(),
                  ButtonWidget(),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

The BannerWidget and ButtomWidget are two Containers similar to the app shown above.

I hope you can help me or tell me maybe what this behaviour is called. Thank you!

Upvotes: 1

Views: 2236

Answers (1)

Igor Kharakhordin
Igor Kharakhordin

Reputation: 9883

If you're ok with using CustomScrollView, you could use SliverPersistentHeader with your own delegate. It will allow you to access current header scroll state and make your own layout depending on how much space you have left.

demo

const double _kSearchHeight = 50.0;
const double _kHeaderHeight = 250.0;

class _ExplorePageState extends State<ExplorePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: CustomScrollView(
          slivers: <Widget>[
            SliverPersistentHeader(
              delegate: DelegateWithSearchBar(),
              pinned: true,
            ),
            SliverList(
              delegate: SliverChildListDelegate(
                [
                  for (int i = 0; i < 4; i++) 
                    Container(
                      height: 200,
                      child: Text('test'),
                      color: Colors.black26
                    ),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}

class DelegateWithSearchBar extends SliverPersistentHeaderDelegate {
  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    final showSearchBar = shrinkOffset > _kHeaderHeight - _kSearchHeight;

    return Stack(
      children: <Widget>[
        AnimatedOpacity(
          opacity: !showSearchBar ? 1 : 0,
          duration: Duration(milliseconds: 100),
          child: LayoutBuilder(
            builder: (context, constraints) {
              return Container(
                decoration: BoxDecoration(
                  image: DecorationImage(
                    image: NetworkImage('xxx'),
                    fit: BoxFit.cover
                  )
                ),
                height: constraints.maxHeight,
                child: SafeArea(
                  child: Container(
                    padding: EdgeInsets.only(left: 20, bottom: 20),
                    alignment: Alignment.bottomLeft,
                    child: Text(
                      'Sample Text',
                      style: TextStyle(color: Colors.white, fontSize: 22)
                    ),
                  ),
                ),
              );
            }
          ),
        ),
        AnimatedOpacity(
          opacity: showSearchBar ? 1 : 0,
          duration: Duration(milliseconds: 100),
          child: Container(
            height: _kSearchHeight,
            color: Colors.white,
            alignment: Alignment.center,
            child: Text('search bar')
          ),
        ),
      ],
    );
  }

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate _) => true;

  @override
  double get maxExtent => _kHeaderHeight;

  @override
  double get minExtent => _kSearchHeight;
}

Upvotes: 3

Related Questions