Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hide bottom navigation bar on scroll down and vice versa

screenshot

I have a list in the body and bottom navigation bar. I want to hide bottom navigation bar with a slide down animation when the posts list is scrolled down and visible with a slide up animation when scrolled up. How to do it?

like image 405
Vithani Ravi Avatar asked Jan 29 '19 05:01

Vithani Ravi


People also ask

What does hide bottom navigation on scroll mean Gmail?

While that smaller navigation bar is greatly appreciated, my Gmail is also showing a “Hide bottom navigation on scroll” option to the settings area. As you can probably understand from its name, checking the box for this option does indeed hide the bottom bar as you scroll.


2 Answers

While Naveen's solution works perfectly, I didn't like the idea of using setState to handle the visibility of the navbar. Every time the user would change scroll direction, the entire homepage including the appbar and body would rebuild which can be an expensive operation. I created a separate class to handle the visibility that uses a ValueNotifier to track the current hidden status.

class HideNavbar {
  final ScrollController controller = ScrollController();
  final ValueNotifier<bool> visible = ValueNotifier<bool>(true);

  HideNavbar() {
    visible.value = true;
    controller.addListener(
      () {
        if (controller.position.userScrollDirection ==
            ScrollDirection.reverse) {
          if (visible.value) {
            visible.value = false;
          }
        }

        if (controller.position.userScrollDirection ==
            ScrollDirection.forward) {
          if (!visible.value) {
            visible.value = true;
          }
        }
      },
    );
  }

  void dispose() {
    controller.dispose();
    visible.dispose();
  }
}

Now all you do is create a final instance of HideNavbar in your HomePage widget.

final HideNavbar hiding = HideNavbar();

Now pass the instance's ScrollController to the ListView or CustomScrollView body of your Scaffold.

body: CustomScrollView(
          controller: hiding.controller,
          ...

Then surround your bottomNavigationBar with a ValueListenableBuilder that takes the ValueNotifier from the HideNavbar instance and then set the height property of the bottomNavigationBar to be either 0 or any other value depending on the status of the ValueNotifier.

bottomNavigationBar: ValueListenableBuilder(
        valueListenable: hiding.visible,
        builder: (context, bool value, child) => AnimatedContainer(
          duration: Duration(milliseconds: 500),
          height: value ? kBottomNavigationBarHeight : 0.0,
          child: Wrap(
            children: <Widget>[
              BottomNavigationBar(
                type: BottomNavigationBarType.fixed,
                backgroundColor: Colors.blue,
                fixedColor: Colors.white,
                unselectedItemColor: Colors.white,
                items: const [
                  BottomNavigationBarItem(
                    icon: Icon(Icons.home),
                    title: Text('Home'),
                  ),
                  BottomNavigationBarItem(
                    icon: Icon(Icons.card_giftcard),
                    title: Text('Offers'),
                  ),
                  BottomNavigationBarItem(
                    icon: Icon(Icons.account_box),
                    title: Text('Account'),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),

This approaches keeps avoids countless rebuilds and doesn't require any external libraries. You can also implement this as a stream-based approach but that would require another library such as dart:async and would not be changing anything. Make sure to call the dispose function of HideNavbar inside HomePage's dispose function to clear all resources used.

like image 85
Kalapapa Avatar answered Oct 14 '22 17:10

Kalapapa


Working code with BottomNavigationBar.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

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

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

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ScrollController _hideBottomNavController;

  bool _isVisible;

  @override
  initState() {
    super.initState();
    _isVisible = true;
    _hideBottomNavController = ScrollController();
    _hideBottomNavController.addListener(
      () {
        if (_hideBottomNavController.position.userScrollDirection ==
            ScrollDirection.reverse) {
          if (_isVisible)
            setState(() {
              _isVisible = false;
            });
        }
        if (_hideBottomNavController.position.userScrollDirection ==
            ScrollDirection.forward) {
          if (!_isVisible)
            setState(() {
              _isVisible = true;
            });
        }
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: CustomScrollView(
          controller: _hideBottomNavController,
          shrinkWrap: true,
          slivers: <Widget>[
            SliverPadding(
              padding: const EdgeInsets.all(10.0),
              sliver: SliverList(
                delegate: SliverChildBuilderDelegate(
                  (context, index) => _getItem(context),
                  childCount: 20,
                ),
              ),
            ),
          ],
        ),
      ),
      bottomNavigationBar: AnimatedContainer(
        duration: Duration(milliseconds: 500),
        height: _isVisible ? 56.0 : 0.0,
        child: Wrap(
          children: <Widget>[
            BottomNavigationBar(
              type: BottomNavigationBarType.fixed,
              backgroundColor: Colors.blue,
              fixedColor: Colors.white,
              unselectedItemColor: Colors.white,
              items: [
                BottomNavigationBarItem(
                  icon: Icon(Icons.home),
                  title: Text('Home'),
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.card_giftcard),
                  title: Text('Offers'),
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.account_box),
                  title: Text('Account'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  _getItem(BuildContext context) {
    return Card(
      elevation: 3,
      margin: EdgeInsets.all(8),
      child: Row(
        children: <Widget>[
          Expanded(
            child: Container(
              padding: EdgeInsets.all(8),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text(
                      'Item',
                      style:
                          TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    ),
                  )
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Working Model

like image 28
Naveen H K Avatar answered Oct 14 '22 18:10

Naveen H K