颤振:使用导航器推送到新屏幕时保持BottomNavigationBar

时间:2018-04-03 11:05:55

标签: flutter navigator bottomnavigationview

在iOS中,当我们推送到新的ViewController时,我们有一个UITabBarController永久保留在屏幕的底部。

在Flutter中,我们有一个脚手架的底部导航栏。但是,与iOS不同,当我们Navigator.push到新屏幕时,这个bottomNavigationBar会消失。

在我的应用中,我希望满足此要求:主屏幕有一个bottomNavigationBar,其中包含2个项目( a &amp; b ),显示屏幕 A &amp; 。默认情况下,会显示屏幕 A 。在屏幕 A 内,有一个按钮。点按该按钮Navigator.push以屏幕 C 。现在在屏幕 C 中,我们仍然可以看到bottomNavigationBar。点按项目 b ,我转到屏幕 B 。现在在屏幕 B 中,点击bottomNavigationBar中的项目 a ,然后返回到屏幕 C (不是 A < / em>, A 当前位于导航层次结构中的 C 之下。

我该怎么做?谢谢,伙计们。

编辑:我要包括一些图片用于演示:

屏幕A. Screen A

点击转到C 按钮,按到屏幕C Screen C

点按底部导航栏中的右侧项,转到屏幕B. Screen B

11 个答案:

答案 0 :(得分:10)

截图:

enter image description here


起点:

void main() => runApp(MaterialApp(home: HomePage()));

HomePage [BottomNavigationBar + Page1]

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Colors.orange,
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.call), label: 'Call'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: 'Message'),
        ],
      ),
      body: Navigator(
        onGenerateRoute: (settings) {
          Widget page = Page1();
          if (settings.name == 'page2') page = Page2();
          return MaterialPageRoute(builder: (_) => page);
        },
      ),
    );
  }
}

第一页:

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Page1')),
      body: Center(
        child: RaisedButton(
          onPressed: () => Navigator.pushNamed(context, 'page2'),
          child: Text('Go to Page2'),
        ),
      ),
    );
  }
}

第二页:

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Scaffold(appBar: AppBar(title: Text('Page2')));
}

答案 1 :(得分:7)

tl; dr:将CupertinoTabBarCupertinoTabScaffold

一起使用

问题不在于Flutter,而在于用户体验,就像RémiRousselet所提到的那样。

事实证明,Material Design不建议层次结构中的子页面访问底部导航栏。

但是,iOS人机界面指南推荐此功能。因此,要使用此功能,我必须调整Cupertino小部件而不是Material小部件。具体来说,在main中,返回包含WidgetsApp/MaterialApp的{​​{1}}。使用CupertinoTabScaffold实施标签栏,每个屏幕都是CupertinoTabBar

答案 2 :(得分:5)

您需要使用路线创建MaterialApp,并使BottomNavigationBar成为它的兄弟。然后使用传递给BottomNavigationBar的MaterialApp.navigatorKey进行导航。

https://medium.com/@swav.kulinski/flutter-navigating-off-the-charts-e118562a36a5

答案 3 :(得分:3)

使用persistent_bottom_nav_bar包,您可以维护单个标签的导航路径,并且当用户也导航到任何屏幕时,BottomNavigationBar都不会消失。

答案 4 :(得分:2)

您实际上可以在体内放置一个占位符 这样的结构

- AppBar
- body (dynamic content from placeholder)
- BottomNavigationBar

然后您将拥有另一个班级作为占位符 因此,每次您点击BottomNavigationBar时,都会刷新身体的内容

我发现的一个例子在这里 https://willowtreeapps.com/ideas/how-to-use-flutter-to-build-an-app-with-bottom-navigation

,但这里太复杂了,对我不起作用 https://medium.com/@swav.kulinski/flutter-navigating-off-the-charts-e118562a36a5

和这个 https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf

答案 5 :(得分:2)

方法1:如果您只想保留BottomNavigationBar,请尝试使用this

选项2:将CupertinoTabBar用于静态BottomNavigationBar

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mqttdemo/Screen2.dart';
import 'package:mqttdemo/Screen3.dart';

import 'Screen1.dart';

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  int _currentIndex;
  List<Widget> _children;

  @override
  void initState() {
    _currentIndex = 0;
    _children = [
      Screen1(),
      Screen2(),
      Screen3(),
    ];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
      tabBar: CupertinoTabBar(
        currentIndex: _currentIndex,
        onTap: onTabTapped,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text("Screen 1"),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text("Screen 2"),
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.home), title: Text("Screen 3")),
        ],

      ),
        tabBuilder: (BuildContext context, int index) {
          return CupertinoTabView(
            builder: (BuildContext context) {
              return SafeArea(
                top: false,
                bottom: false,
                child: CupertinoApp(
                  home: CupertinoPageScaffold(
                    resizeToAvoidBottomInset: false,
                    child: _children[_currentIndex],
                  ),
                ),
              );
            },
          );
        }
    );
  }

  void onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }
}

从Screen3导航到screen4,如下所示:

    class Screen3 extends StatefulWidget {
      @override
      _Screen3State createState() => _Screen3State();
    }
    
    class _Screen3State extends State<Screen3> {
      @override
      Widget build(BuildContext context) {
        return Container(
          color: Colors.black,
          child: Center(
            child: RaisedButton(
              child: Text("Click me"),
              onPressed: () {
                Navigator.of(context, rootNavigator: false).push(MaterialPageRoute(
                    builder: (context) => Screen4(), maintainState: false));
              },
            ),
          ),
        );
      }

}

答案 6 :(得分:1)

我认为这样做的#right方式是将BottomNavigationBar包含在Hero中并且在两种情况下使用相同的标记。这样,当页面之间的动画发生时,它们将被排除。

这是我可以做的一个简短的示例,但我强烈建议清理它,即使用小部件而不是大块构建传递英雄字符串,为BottomNavigationBar制作自己的小部件。

请注意,在英雄转换期间,它至少会在我的手机上overflow by 0.0000191 pixels,但在发布模式下,这不是一个我不会想到的问题。

import&#39; package:flutter / material.dart&#39;;

void main() => runApp(new MaterialApp(
      home: new Builder(
        builder: (context) => new Scaffold(
              bottomNavigationBar: new Hero(
                tag: "bottomNavigationBar",
                child: new BottomNavigationBar(items: [
                  new BottomNavigationBarItem(icon: new Icon(Icons.home), title: new Text("Home")),
                  new BottomNavigationBarItem(icon: new Icon(Icons.ac_unit), title: new Text("AC Unit"))
                ]),
              ),
              body: new SafeArea(
                child: new Container(
                  constraints: new BoxConstraints.expand(),
                  color: Colors.green,
                  child: new Column(
                    children: <Widget>[
                      new RaisedButton(
                          child: new Text("Press me"),
                          onPressed: () {
                            Navigator.push(
                                context,
                                new MaterialPageRoute(
                                    builder: (context) => new Scaffold(
                                          bottomNavigationBar: new Hero(
                                            tag: "bottomNavigationBar",
                                            child: new BottomNavigationBar(items: [
                                              new BottomNavigationBarItem(icon: new Icon(Icons.home), title: new Text("Home")),
                                              new BottomNavigationBarItem(icon: new Icon(Icons.ac_unit), title: new Text("AC Unit"))
                                            ]),
                                          ),
                                          body: new SafeArea(
                                            child: new Container(
                                              constraints:
                                                  new BoxConstraints.expand(),
                                              color: Colors.red,
                                              child: new Column(
                                                children: <Widget>[
                                                  new RaisedButton(
                                                    onPressed: () =>
                                                        Navigator.pop(context),
                                                    child: new Text("Back"),
                                                  )
                                                ],
                                              ),
                                            ),
                                          ),
                                        )));
                          })
                    ],
                  ),
                ),
              ),
            ),
      ),
    ));

我不知道hero系统处理多个英雄等有多好,如果你说想要为导航栏设置动画,这可能效果不太好。

还有另一种方法可以让你为底部导航栏设置动画;它实际上是一个已经回答的问题:Flutter: Hero transition + widget animation at the same time?

答案 7 :(得分:1)

实现此目的的另一种方法(尽管不是很好的做法)是将材质应用程序嵌套在支架的主体中。并在那里处理所有“子导航”。

因此,您的层次结构将如下所示

<div class="text-block">
  <p>basically I want to implement a banner CTA block which would always go over the previous and next DIVs but not create a white, empty spacing. I want to implement this in fluid way, irrespective of the height of the actual CTA banner block.</p>
  <div class="cta">basically I want to implement a banner CTA block which would always go over the previous and next DIVs but not create a white, empty spacing. I want to implement this in fluid way, irrespective of the height of the actual CTA banner block.</div>
</div>

我已经尝试过了,效果很好。不幸的是,我现在无法发布源代码。

答案 8 :(得分:0)

您可以在Navigator小部件中创建Stack小部件,以将BottomNavigationBar用于标签的内部导航。您可以使用WillPopScope处理Android的后退按钮以弹出标签的内部屏幕。另外,双击底部导航项以弹出选项卡的所有内部屏幕。

我已经创建了一个Sample app 为此。

希望获得帮助!

答案 9 :(得分:0)

我创建了一个小巧,易于使用的软件包,可以让您实现这种效果CustomNavigator 并在Medium上撰写了有关它的教程here

答案 10 :(得分:0)

您可以使用auto_route包的嵌套路由,这就是我实现它的方式。如果有人想参考auto_route

,请点击这里

实现此目标的另一种方法是使用get软件包。

不建议使用嵌套路由,因为它们在某些情况下会造成内存泄漏。