J'ai une liste dans le corps et la barre de navigation inférieure. Je souhaite masquer la barre de navigation inférieure avec une animation de glissement vers le bas lorsque la liste des articles est défilée vers le bas et visible avec une animation de glissement vers le haut lorsqu'elle fait défiler vers le haut. Comment faire?
3 Réponses :
Voici le 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)), ], ); } }
Remarque : j'ai essayé bottomNavigationBar
mais les choses ne fonctionnaient pas comme prévu, j'ai donc créé une sorte de barre de navigation inférieure et vous pouvez modifier davantage le code pour votre usage.
Code de travail avec 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), ), ) ], ), ), ), ], ), ); } }
Cela devrait être la réponse acceptée pour le moment! Au moins jusqu'à ce que l'équipe Flutter fasse une mise à niveau
Bien que la solution de Naveen fonctionne parfaitement, je n'ai pas aimé l'idée d'utiliser setState
pour gérer la visibilité de la barre de navigation. Chaque fois que l'utilisateur change de direction de défilement, la page d'accueil entière, y compris la barre d'applications et le corps, se reconstruit, ce qui peut être une opération coûteuse. J'ai créé une classe distincte pour gérer la visibilité qui utilise un ValueNotifier
pour suivre l'état masqué actuel.
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'), ), ], ), ], ), ), ),
Il ne vous reste plus qu'à créer une instance finale de HideNavbar
dans votre widget HomePage.
body: CustomScrollView( controller: hiding.controller, ...
Passez maintenant le ScrollController
de l'instance à ListView
ou CustomScrollView corps de votre Scaffold
.
final HideNavbar hiding = HideNavbar();
Puis entourez votre bottomNavigationBar
d'un ValueListenableBuilder
qui prend le ValueNotifier
de l'instance HideNavbar
et définit la propriété height de bottomNavigationBar sur 0 ou sur toute autre valeur en fonction de l'état du ValueNotifier
.
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; } } }, ); } }
Cette approche garde la page d'accueil comme un StatelessWidget, évite d'innombrables reconstructions et ne nécessite aucune bibliothèque externe. Vous pouvez également l'implémenter en tant qu'approche basée sur le flux, mais cela nécessiterait une autre bibliothèque telle que dart: async
et ne changerait rien.