Hide bottom navigation bar on scroll down and vice versa

前端 未结 3 619
囚心锁ツ
囚心锁ツ 2020-12-19 13:36

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 visib

相关标签:
3条回答
  • 2020-12-19 14:04

    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

    0 讨论(0)
  • 2020-12-19 14:06

    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();
      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;
              }
            }
          },
        );
      }
    }
    

    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 the 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 ? 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'),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
    

    This approaches keeps the HomePage as a StatelessWidget, 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.

    0 讨论(0)
  • 2020-12-19 14:08

    Here is the code.

    void main() => runApp(MaterialApp(home: Scaffold(body: MyApp())));
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      ScrollController _scrollController;
      double _containerMaxHeight = 56, _offset, _delta = 0, _oldOffset = 0;
    
      @override
      void initState() {
        super.initState();
        _offset = 0;
        _scrollController = ScrollController()
          ..addListener(() {
            setState(() {
              double offset = _scrollController.offset;
              _delta += (offset - _oldOffset);
              if (_delta > _containerMaxHeight)
                _delta = _containerMaxHeight;
              else if (_delta < 0) _delta = 0;
              _oldOffset = offset;
              _offset = -_delta;
            });
          });
      }
    
      @override
      Widget build(BuildContext context) {
        return LayoutBuilder(
          builder: (context, constraints) {
            return Stack(
              alignment: Alignment.bottomCenter,
              children: <Widget>[
                ListView.builder(
                  physics: ClampingScrollPhysics(),
                  controller: _scrollController,
                  itemCount: 20,
                  itemBuilder: (context, index) => ListTile(title: Text(index.toString())),
                ),
                Positioned(
                  bottom: _offset,
                  width: constraints.maxWidth,
                  child: Container(
                    width: double.infinity,
                    height: _containerMaxHeight,
                    color: Colors.grey[300],
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: <Widget>[
                        _buildItem(Icons.home, "Home"),
                        _buildItem(Icons.blur_circular, "Collection"),
                        _buildItem(Icons.supervised_user_circle, "Community"),
                        _buildItem(Icons.notifications, "Notifications"),
                      ],
                    ),
                  ),
                ),
              ],
            );
          },
        );
      }
    
      Widget _buildItem(IconData icon, String title) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(icon, size: 28),
            Text(title, style: TextStyle(fontSize: 10)),
          ],
        );
      }
    }
    

    Note: I tried bottomNavigationBar but things were not working as expected so I created kind of my own bottom navigation bar and you can modify the code further for your use.

    Screenshot

    0 讨论(0)
提交回复
热议问题