Flutter:使用 Navigator 推送到新屏幕时保持 BottomNavigationBar
Flutter: Keep BottomNavigationBar When Push to New Screen with Navigator
在 iOS 中,我们有一个 UITabBarController,当我们推送到新的 ViewController 时,它会永久停留在屏幕底部。
在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
我认为#right 的做法是在两种情况下使用相同的标签将 BottomNavigationBar 包裹在 Hero 中。这样,当页面之间的动画发生时,它们将被排除。
这是我能做的尽可能简短的示例,但我强烈建议您对其进行清理,即传递英雄字符串,使用小部件而不是大量构建,为 BottomNavigationBar 制作您自己的小部件。
请注意,在英雄转换期间,它至少在我的 phone 上 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
系统处理多个英雄等的效果如何,如果你说想要为导航栏设置动画,这可能不太好。
还有另一种方法可以使底部导航栏具有动画效果;这实际上是一个已经回答的问题:
tl;dr:使用 CupertinoTabBar with CupertinoTabScaffold
问题不在 Flutter 中,而是在 UX 中,就像 Rémi Rousselet 提到的那样。
原来Material设计不建议sub-pages在层次结构中访问底部导航栏。
但是,iOS 人机界面指南建议这样做。因此,要使用此功能,我必须调整 Cupertino 小部件而不是 Material 小部件。具体来说,在 main 中,return 一个 WidgetsApp/MaterialApp
其中包含一个 CupertinoTabScaffold
。使用 CupertinoTabBar
实现标签栏,每个屏幕都是 CupertinoTabView
.
另一种实现此目的的方法(虽然不是好的做法)是在脚手架的主体中嵌套一个 material 应用程序。并在那里处理所有 "sub-navigation"。
因此,您的层次结构将如下所示
Material App
- home
- Scaffold
- body
- Material App
- Scaffold
- AppBar
- body
...
- routes (internal)
- bottomNavigationBar
- routes (external)
我试过了,效果很好。不幸的是我现在无法 post 源代码。
你实际上可以在正文中放置一个占位符
所以这样的结构
- AppBar
- body (dynamic content from placeholder)
- BottomNavigationBar
然后你会有另一个 class 作为占位符
所以每次你点击 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
您可以在 Stack
小部件中创建 Navigator
小部件,以将 BottomNavigationBar
用于选项卡的内部导航。您可以使用 WillPopScope
来处理 Android 的后退按钮以弹出选项卡的内部屏幕。此外,双击底部导航项可弹出标签页的所有内部屏幕。
我创建了一个 Sample app
为此。
希望对您有所帮助!
选项 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));
},
),
),
);
}
}
截图:
起点:
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);
},
),
);
}
}
第 1 页:
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'),
),
),
);
}
}
第2页:
class Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) => Scaffold(appBar: AppBar(title: Text('Page2')));
}
在 iOS 中,我们有一个 UITabBarController,当我们推送到新的 ViewController 时,它会永久停留在屏幕底部。
在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
我认为#right 的做法是在两种情况下使用相同的标签将 BottomNavigationBar 包裹在 Hero 中。这样,当页面之间的动画发生时,它们将被排除。
这是我能做的尽可能简短的示例,但我强烈建议您对其进行清理,即传递英雄字符串,使用小部件而不是大量构建,为 BottomNavigationBar 制作您自己的小部件。
请注意,在英雄转换期间,它至少在我的 phone 上 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
系统处理多个英雄等的效果如何,如果你说想要为导航栏设置动画,这可能不太好。
还有另一种方法可以使底部导航栏具有动画效果;这实际上是一个已经回答的问题:
tl;dr:使用 CupertinoTabBar with CupertinoTabScaffold
问题不在 Flutter 中,而是在 UX 中,就像 Rémi Rousselet 提到的那样。
原来Material设计不建议sub-pages在层次结构中访问底部导航栏。
但是,iOS 人机界面指南建议这样做。因此,要使用此功能,我必须调整 Cupertino 小部件而不是 Material 小部件。具体来说,在 main 中,return 一个 WidgetsApp/MaterialApp
其中包含一个 CupertinoTabScaffold
。使用 CupertinoTabBar
实现标签栏,每个屏幕都是 CupertinoTabView
.
另一种实现此目的的方法(虽然不是好的做法)是在脚手架的主体中嵌套一个 material 应用程序。并在那里处理所有 "sub-navigation"。
因此,您的层次结构将如下所示
Material App
- home
- Scaffold
- body
- Material App
- Scaffold
- AppBar
- body
...
- routes (internal)
- bottomNavigationBar
- routes (external)
我试过了,效果很好。不幸的是我现在无法 post 源代码。
你实际上可以在正文中放置一个占位符 所以这样的结构
- AppBar
- body (dynamic content from placeholder)
- BottomNavigationBar
然后你会有另一个 class 作为占位符 所以每次你点击 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
您可以在 Stack
小部件中创建 Navigator
小部件,以将 BottomNavigationBar
用于选项卡的内部导航。您可以使用 WillPopScope
来处理 Android 的后退按钮以弹出选项卡的内部屏幕。此外,双击底部导航项可弹出标签页的所有内部屏幕。
我创建了一个 Sample app 为此。
希望对您有所帮助!
选项 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));
},
),
),
);
}
}
截图:
起点:
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);
},
),
);
}
}
第 1 页:
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'),
),
),
);
}
}
第2页:
class Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) => Scaffold(appBar: AppBar(title: Text('Page2')));
}