【问题标题】:Flutter: Keep BottomNavigationBar When Push to New Screen with NavigatorFlutter:使用导航器推送到新屏幕时保持底部导航栏
【发布时间】:2018-09-12 16:30:09
【问题描述】:

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

在 Flutter 中,我们有一个 Scaffold 的 bottomNavigationBar。但是,与 iOS 不同的是,当我们Navigator.push 到一个新屏幕时,这个bottomNavigationBar 会消失。

在我的应用程序中,我想满足这个要求:主屏幕有一个 bottomNavigationBar 和 2 个项目 (a & b) 呈现屏幕 A & B。默认情况下会显示屏幕A。在屏幕A内,有一个按钮。点击该按钮,Navigator.push 以屏幕 C。现在在屏幕C中,我们仍然可以看到bottomNavigationBar。点击项目b,我进入屏幕B。现在在屏幕B中,点击bottomNavigationBar中的项目a,我回到屏幕C(不是A,A 当前在导航层次结构中位于 C 之下)。

我该怎么做?谢谢各位。

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

屏幕 A Screen A

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

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

【问题讨论】:

  • 你所说的button是在BottomNavigationBar里面吗?
  • 不,按钮不在底部栏中。它在主屏幕内。这只是触发Navigator 到push 到新屏幕的东西。
  • 我认为这更像是一个用户体验问题。因为你的 C 视图不应该有底部导航栏。或者,应该可以从该底部栏访问 C。
  • 同一层次中的屏幕能够拥有底部导航栏不是很常见吗?以 Twitter 为例(请打开 iOS Twitter 应用程序),点击一条推文,TweetViewController 被推送,底部栏仍然可见。我认为几乎所有流行的应用都有这种行为。
  • 我同意 Harry 的说法,这在 iOS 中是很常见的事情,而 TBH Flutter 为整个屏幕设置动画的方式实际上与 iOS 处理导航栏的方式有点相反——尽管 iOS modal popup 输入屏幕时会覆盖导航栏。

标签: flutter navigator bottomnavigationview


【解决方案1】:

tl;dr:使用 CupertinoTabBar 和 CupertinoTabScaffold

问题不在于 Flutter,而在于 UX,就像 Rémi Rousselet 提到的那样。

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

但是,iOS 人机界面指南建议这样做。因此,要使用此功能,我必须调整 Cupertino 小部件而不是 Material 小部件。具体来说,主要返回一个WidgetsApp/MaterialApp,其中包含一个CupertinoTabScaffold。使用CupertinoTabBar 实现标签栏,每个屏幕都是CupertinoTabView。

【讨论】:

【解决方案2】:

截图:


起点:

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')));
}

【讨论】:

  • 像魅力一样工作。非常感谢
  • 找不到路由RouteSettings的生成器
  • 以及如何在没有底部导航栏的情况下导航一些屏幕,你能解释一下吗?
  • 您可以使用root navigator:Navigator.of(context, rootNavigator: true).pushFunction(...),但您还需要在root中配置路由,例如在materialApp中设置routes属性
  • @KunYuTsai 将onChanged 属性添加到BottomNavigationBar 小部件,使用索引来跟踪当前页面,然后使用if-else 条件,例如,选择要使用哪个小部件显示。
【解决方案3】:

您实际上可以在正文中放置一个占位符 所以结构是这样的

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

然后你会有另一个类作为占位符 所以每次点击 BottomNavigationBar 都会刷新 body 的内容

我发现的一个例子是here 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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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

      Material App
        - home
           - Scaffold
             - body
               - Material App
                    - Scaffold
                        - AppBar
                        - body
                        ...
               - routes (internal)
             - bottomNavigationBar
        - routes (external)
      

      我试过了,效果很好。很遗憾,我现在无法发布源代码。

      【讨论】:

        【解决方案6】:

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

        我创建了一个Sample app 为此。

        希望对您有所帮助!

        【讨论】:

          【解决方案7】:

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

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

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

          导入'package:flutter/material.dart';

          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?

          【讨论】:

            猜你喜欢
            • 2021-12-19
            • 2020-03-06
            • 1970-01-01
            • 2018-10-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-10-13
            • 1970-01-01
            相关资源
            最近更新 更多