【问题标题】:How to use BottomNavigationBar with Navigator?如何将 BottomNavigationBar 与 Navigator 一起使用?
【发布时间】:2017-12-27 09:10:15
【问题描述】:

BottomNavigationBar 的 Flutter Gallery 示例在 Scaffold 的正文中使用了 StackFadeTransitions

我觉得如果我们可以使用Navigator 切换页面会更简洁(并且更容易制作动画)。

有这方面的例子吗?

【问题讨论】:

    标签: flutter dart flutter-layout bottomnavigationview navigator


    【解决方案1】:
    int index = 0;
    
    @override
    Widget build(BuildContext context) {
      return new Scaffold(
        body: new Stack(
          children: <Widget>[
            new Offstage(
              offstage: index != 0,
              child: new TickerMode(
                enabled: index == 0,
                child: new MaterialApp(home: new YourLeftPage()),
              ),
            ),
            new Offstage(
              offstage: index != 1,
              child: new TickerMode(
                enabled: index == 1,
                child: new MaterialApp(home: new YourRightPage()),
              ),
            ),
          ],
        ),
        bottomNavigationBar: new BottomNavigationBar(
          currentIndex: index,
          onTap: (int index) { setState((){ this.index = index; }); },
          items: <BottomNavigationBarItem>[
            new BottomNavigationBarItem(
              icon: new Icon(Icons.home),
              title: new Text("Left"),
            ),
            new BottomNavigationBarItem(
              icon: new Icon(Icons.search),
              title: new Text("Right"),
            ),
          ],
        ),
      );
    }
    

    您应该通过Stack 保留每个页面以保持其状态。 Offstage 停止绘画,TickerMode 停止动画。 MaterialApp 包括 Navigator

    【讨论】:

    • 您能否详细说明 TickerMode 小部件的用途?此代码似乎也可以正常工作: new Offstage( offstage: _tabIndex != 1, child: new YourRightPage(), ),
    • 使用此实现,后退按钮无法访问之前的 Offstage 小部件。有什么办法支持吗?
    • @RafaelHamasaki 您可以使用docs.flutter.io/flutter/widgets/WidgetsBindingObserver/…处理后退按钮
    • @CodeGrue 我使用 TickerMode 来停止未显示的小部件的动画。
    • 另一种可能是IndexedStack,它为StackOffstage 提供了一个更简单的API。你可能仍然需要TickerMode
    【解决方案2】:

    输出:

    代码:

    int _index = 0;
    
    @override
    Widget build(BuildContext context) {
      Widget child;
      switch (_index) {
        case 0:
          child = FlutterLogo();
          break;
        case 1:
          child = FlutterLogo(colors: Colors.orange);
          break;
        case 2:
          child = FlutterLogo(colors: Colors.red);
          break;
      }
    
      return Scaffold(
        body: SizedBox.expand(child: child),
        bottomNavigationBar: BottomNavigationBar(
          onTap: (newIndex) => setState(() => _index = newIndex),
          currentIndex: _index,
          items: [
            BottomNavigationBarItem(icon: Icon(Icons.looks_one), title: Text("Blue")),
            BottomNavigationBarItem(icon: Icon(Icons.looks_two), title: Text("Orange")),
            BottomNavigationBarItem(icon: Icon(Icons.looks_3), title: Text("Red")),
          ],
        ),
      );
    }
    

    【讨论】:

    • 不幸的是,据我了解,您的解决方案仅在 Scaffold 主体是相同的小部件但具有不同的属性时才有效,这不是常规情况。
    • 这对我的简单实现帮助很大。非常感谢
    • 这很简单,如果你能帮我解决这个问题,那就太好了
    • @AmanpreetKaur 为此,您可以使用Set。在按下新项目时,只需将其索引添加到集合中,将小部件包装在 WillPopScopeonWillPop 内,您可以检查最后一个索引的值,如果它为空,只需退出应用程序,否则将当前索引设置为那个值。
    【解决方案3】:

    完整的例子

    先做一个类MyBottomBarDemo

    class MyBottomBarDemo extends StatefulWidget {
      @override
      _MyBottomBarDemoState createState() => new _MyBottomBarDemoState();
    }
    
    class _MyBottomBarDemoState extends State<MyBottomBarDemo> {
      int _pageIndex = 0;
      PageController _pageController;
    
      List<Widget> tabPages = [
        Screen1(),
        Screen2(),
        Screen3(),
      ];
    
      @override
      void initState(){
        super.initState();
        _pageController = PageController(initialPage: _pageIndex);
      }
    
      @override
      void dispose() {
        _pageController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("BottomNavigationBar", style: TextStyle(color: Colors.white)),
            backgroundColor: Colors.deepPurple,
          ),
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: _pageIndex,
            onTap: onTabTapped,
            backgroundColor: Colors.white,
            items: <BottomNavigationBarItem>[
              BottomNavigationBarItem( icon: Icon(Icons.home), title: Text("Home")),
              BottomNavigationBarItem(icon: Icon(Icons.mail), title: Text("Messages")),
              BottomNavigationBarItem(icon: Icon(Icons.person), title: Text("Profile")),
            ],
    
          ),
          body: PageView(
            children: tabPages,
            onPageChanged: onPageChanged,
            controller: _pageController,
          ),
        );
      }
      void onPageChanged(int page) {
        setState(() {
          this._pageIndex = page;
        });
      }
    
      void onTabTapped(int index) {
        this._pageController.animateToPage(index,duration: const Duration(milliseconds: 500),curve: Curves.easeInOut);
      }
    }
    

    然后创建一个你的屏幕

    class Screen1 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
            color: Colors.green,
                child: Center(child: Text("Screen 1")),
        );
      }
    }
    
    class Screen2 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          color: Colors.yellow,
          child: Center(child: Text("Screen 2")),
        );
      }
    }
    
    class Screen3 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          color: Colors.cyan,
          child: Center(child: Text("Screen 3")),
        );
      }
    }
    

    【讨论】:

    • 假设我在主页上,我想转到消息的单页。如何在主页上单击按钮转到该路线?
    【解决方案4】:

    这是一个示例,您可以如何使用 Navigator 和 BottomNavigationBar 来导航不同的屏幕。

    import 'package:flutter/material.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 Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      // This navigator state will be used to navigate different pages
      final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
      int _currentTabIndex = 0;
    
      @override
      Widget build(BuildContext context) {
        return SafeArea(
          child: Scaffold(
            body: Navigator(key: _navigatorKey, onGenerateRoute: generateRoute),
            bottomNavigationBar: _bottomNavigationBar(),
          ),
        );
      }
    
      Widget _bottomNavigationBar() {
        return BottomNavigationBar(
          type: BottomNavigationBarType.fixed,
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              title: Text("Home"),
            ),
            BottomNavigationBarItem(
                icon: Icon(Icons.account_circle), title: Text("Account")),
            BottomNavigationBarItem(
              icon: Icon(Icons.settings),
              title: Text("Settings"),
            )
          ],
          onTap: _onTap,
          currentIndex: _currentTabIndex,
        );
      }
    
      _onTap(int tabIndex) {
        switch (tabIndex) {
          case 0:
            _navigatorKey.currentState.pushReplacementNamed("Home");
            break;
          case 1:
            _navigatorKey.currentState.pushReplacementNamed("Account");
            break;
          case 2:
            _navigatorKey.currentState.pushReplacementNamed("Settings");
            break;
        }
        setState(() {
          _currentTabIndex = tabIndex;
        });
      }
    
      Route<dynamic> generateRoute(RouteSettings settings) {
        switch (settings.name) {
          case "Account":
            return MaterialPageRoute(builder: (context) => Container(color: Colors.blue,child: Center(child: Text("Account"))));
          case "Settings":
            return MaterialPageRoute(builder: (context) => Container(color: Colors.green,child: Center(child: Text("Settings"))));
          default:
            return MaterialPageRoute(builder: (context) => Container(color: Colors.white,child: Center(child: Text("Home"))));
        }
      }
    }
    

    【讨论】:

    • 您能否详细说明这一行:body: Navigator(key: _navigatorKey, onGenerateRoute: generateRoute), 可以将 Navigator 小部件设置为您的应用程序的主体吗?看起来很奇怪。它是如何工作的?
    • 有一两个关于这个主题的高度复杂的教程,包含使它们基本上无用的缺陷。这个简短的 sn-p 是唯一有效的。问题是您需要将 pushNamed 传递给导航键。谢谢你,干得好。
    • @KirillKarmazin 如果您想在不同的小部件之间导航。您必须将它们放在小部件树中的导航器下。因为在本例中,我们只想在导航期间更改屏幕主体。所以我将导航器添加为 body 中的顶部小部件,并将其他小部件放在它下面。
    • 解释得很好。我唯一做的就是将 ChangeNotifier Provider 用于选定的选项卡索引。这样,即使我从页面内路由到页面(底部栏页面),它也会激活所选索引。
    • PushReplacementNamed 将清除堆栈中的最后一个屏幕。您需要导航中的所有部分同时存在,并且它们自己的导航器完好无损。我发现这样做的唯一方法是使用 IndexedStack,但我不使用 Navigator。仍在寻找好的解决方案。
    【解决方案5】:

    示例如下:

      int _currentIndex = 0;
    
    
      Route<Null> _getRoute(RouteSettings settings) {
        final initialSettings = new RouteSettings(
            name: settings.name,
            isInitialRoute: true);
    
        return new MaterialPageRoute<Null>(
            settings: initialSettings,
            builder: (context) =>
            new Scaffold(
              body: new Center(
                  child: new Container(
                      height: 200.0,
                      width: 200.0,
                      child: new Column(children: <Widget>[
                        new Text(settings.name),
                        new FlatButton(onPressed: () =>
                            Navigator.of(context).pushNamed(
                                "${settings.name}/next"), child: new Text("push")),
                      ],
                      ))
              ),
              bottomNavigationBar: new BottomNavigationBar(
                  currentIndex: _currentIndex,
                  onTap: (value) {
                    final routes = ["/list", "/map"];
                    _currentIndex = value;
                    Navigator.of(context).pushNamedAndRemoveUntil(
                        routes[value], (route) => false);
                  },
                  items: [
                    new BottomNavigationBarItem(
                        icon: new Icon(Icons.list), title: new Text("List")),
                    new BottomNavigationBarItem(
                        icon: new Icon(Icons.map), title: new Text("Map")),
                  ]),
            ));
      }
    
      @override
      Widget build(BuildContext context) =>
          new MaterialApp(
            initialRoute: "/list",
            onGenerateRoute: _getRoute,
            theme: new ThemeData(
              primarySwatch: Colors.blue,
            ),
          );
    

    您可以将isInitialRoute 设置为true 并将其传递给MaterialPageRoute。它将删除弹出动画。

    要删除旧路线,您可以使用pushNamedAndRemoveUntil

    Navigator.of(context).pushNamedAndRemoveUntil(routes[value], (route) => false);
    

    要设置当前页面,您可以在状态_currentIndex 中设置一个变量并将其分配给BottomNavigationBar

    【讨论】:

    • 有趣。 BottomNavigationBar 是否在整个导航更改中一直存在?我还需要它来反映当前页面。
    • BottomNavigationBar 不粘,大致我们每次都创建一个新的,但状态正确。
    • 使用当前索引更新示例
    【解决方案6】:
    Navigator.of(context).pushNamedAndRemoveUntil(
                    routes[value], (route) => true);
    

    我必须使用 true 来启用后退按钮。

    注意:我使用Navigator.pushNamed() 进行导航。

    【讨论】:

      猜你喜欢
      • 2019-05-24
      • 1970-01-01
      • 1970-01-01
      • 2018-01-13
      • 2021-09-09
      • 2021-11-25
      • 2021-10-12
      • 2021-04-17
      • 1970-01-01
      相关资源
      最近更新 更多