我想在 Flutter 中利用嵌套路由。我想除了一件事之外我已经做到了我想要的。我的嵌套“主页”路线不会在
AppBar
中显示返回导航,但是硬件按钮将使我回到上一条路线。也许我没有正确使用嵌套路由。
我有一个根
MaterialApp
,它通过routes
属性定义主根页面。根页面之一是 MyChildPage
,它呈现另一个 Navigator
并使用 onGenerateRoute
属性将用户指向其子页面。导航可以工作,但是当我登陆 MyChildPage
(它的标题为“子页面”)时,后退按钮丢失了:
您可以在 dartpad.dev 上运行交互式示例,其中代码也可见。
我将所有内容都放在一个文件中:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested tutorial',
theme: ThemeData(
primarySwatch: Colors.orange,
),
initialRoute: '/',
routes: {
'/': (_) => MyHomePage(),
'/sub_page': (_) => MySubPage(),
'/child': (_) => MyChildPage(),
});
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Hello this is home page'),
ElevatedButton(
child: Text('Visit sub page'),
onPressed: () {
Navigator.pushNamed(context, '/sub_page');
}),
OutlinedButton(
child: Text('Visit child page'),
onPressed: () {
Navigator.pushNamed(context, '/child');
}),
],
),
),
);
}
}
class MySubPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home / Sub Page')),
body: Center(child: Text('Hello this is sub page')),
);
}
}
class MyChildPage extends StatefulWidget {
@override
_MyChildPageState createState() => _MyChildPageState();
}
class _MyChildPageState extends State<MyChildPage> {
@override
Widget build(BuildContext context) {
return Navigator(
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/child_1':
return MaterialPageRoute(builder: (_) => MyChildPage1());
case '/child_2':
return MaterialPageRoute(builder: (_) => MyChildPage2());
case '/':
default:
return MaterialPageRoute(builder: (_) => MyChildPageRoot());
}
});
}
}
class MyChildPageRoot extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Child page')),
body: Container(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Center(child: Text('This is child page root ')),
Center(
child: OutlinedButton(
child: Text('Visit sub child page 1'),
onPressed: () {
Navigator.pushNamed(context, '/child_1');
})),
Center(
child: OutlinedButton(
child: Text('Visit sub child page 2'),
onPressed: () {
Navigator.pushNamed(context, '/child_2');
})),
])),
);
}
}
class MyChildPage1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Child sub page 1')),
body: Center(child: Text('You are on child sub page #1')),
);
}
}
class MyChildPage2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Child sub page 2')),
body: Center(child: Text('You are on child sub page #2')),
);
}
}
我正在寻找一个仍然可以使用命名路由的解决方案,并且我想保留多个导航器的结构。我不想在我的顶级
MaterialApp
小部件中定义所有路由。
经过一些网上研究,我想我已经找到了一些解决方案。
后退箭头未显示在
AppBar
中的第一个问题是由于为每条路线(Scaffold
、MyChildPageRoot
和 MyChildPage1
)定义单独的 MyChildPage2
引起的。在 _MyChildPageState
中创建单个脚手架并在其 Navigator
内渲染 body
解决了问题。
虽然这可能不是最灵活的解决方案(可能有原因导致您需要每条路线使用不同的脚手架),但我还没有找到不同的方法。
修复此问题后,后退导航可以工作,但子路线将返回到应用程序路线(初始屏幕),而不是返回到
MyChildPageRoot
。
我设法通过使用
Scaffold
小部件将 _MyChildPageState
包装在 WillPopScope
中并为嵌套导航器创建单独的导航器键来解决此问题:
class _MyChildPageState extends State<MyChildPage> {
GlobalKey<NavigatorState> _navigatorKey = GlobalKey();
// <-- snip -->
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final shouldPop = await _navigatorKey.currentState?.maybePop();
return shouldPop == null ? true : !shouldPop;
},
child: Scaffold(
appBar: AppBar(title: Text(_title)),
// <-- snip -->
这现在会导致正确的后退按钮行为。我不喜欢这种只有顶级
Scaffold
的解决方案,因为我必须在访问路线时以编程方式/命令式更改 AppBar
标题,但也许这只是很多情况下的边缘情况人。
这是结果:
链接到 dartpad.dev 以获取工作示例。
更新后的代码:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested tutorial',
theme: ThemeData(
primarySwatch: Colors.orange,
),
initialRoute: '/',
routes: {
'/': (_) => MyHomePage(),
'/sub_page': (_) => MySubPage(),
'/child': (_) => MyChildPage(),
});
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Hello this is home page'),
ElevatedButton(
child: Text('Visit sub page'),
onPressed: () {
Navigator.pushNamed(context, '/sub_page');
}),
OutlinedButton(
child: Text('Visit child page'),
onPressed: () {
Navigator.pushNamed(context, '/child');
}),
],
),
),
);
}
}
class MySubPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home / Sub Page')),
body: Center(child: Text('Hello this is sub page')),
);
}
}
class MyChildPage extends StatefulWidget {
@override
_MyChildPageState createState() => _MyChildPageState();
}
class _MyChildPageState extends State<MyChildPage> {
GlobalKey<NavigatorState> _navigatorKey = GlobalKey();
String _title = 'Child page';
void _setTitle(String title) {
setState(() {
_title = title;
});
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
_setTitle('Child page');
final shouldPop = await _navigatorKey.currentState?.maybePop();
return shouldPop == null ? true : !shouldPop;
},
child: Scaffold(
appBar: AppBar(title: Text(_title)),
body: Navigator(
key: _navigatorKey,
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/child_1':
return MaterialPageRoute(
builder: (_) => MyChildPage1(setTitle: _setTitle));
case '/child_2':
return MaterialPageRoute(
builder: (_) => MyChildPage2(setTitle: _setTitle));
case '/':
default:
return MaterialPageRoute(
builder: (_) => MyChildPageRoot());
}
})));
}
}
class MyChildPageRoot extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Center(child: Text('This is child page root ')),
Center(
child: OutlinedButton(
child: Text('Visit sub child page 1'),
onPressed: () {
Navigator.pushNamed(context, '/child_1');
})),
Center(
child: OutlinedButton(
child: Text('Visit sub child page 2'),
onPressed: () {
Navigator.pushNamed(context, '/child_2');
})),
]),
);
}
}
class MyChildPage1 extends StatefulWidget {
MyChildPage1({required this.setTitle});
final Function setTitle;
@override
_MyChildPage1State createState() => _MyChildPage1State();
}
class _MyChildPage1State extends State<MyChildPage1> {
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
widget.setTitle('Child page / 1');
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Center(child: Text('You are on child sub page #1')),
);
}
}
class MyChildPage2 extends StatefulWidget {
MyChildPage2({required this.setTitle});
final Function setTitle;
@override
_MyChildPage2State createState() => _MyChildPage2State();
}
class _MyChildPage2State extends State<MyChildPage2> {
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
widget.setTitle('Child page / 2');
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Center(child: Text('You are on child sub page #2')),
);
}
}
问题出在初始路线上。您使用初始路线创建了两个课程:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested tutorial',
theme: ThemeData(
primarySwatch: Colors.orange,
),
initialRoute: '/',
routes: {
'/': (_) => MyHomePage(),
'/sub_page': (_) => MySubPage(),
'/child': (_) => MyChildPage(),
});
}
}
还有
class MyChildPage extends StatefulWidget {
@override
_MyChildPageState createState() => _MyChildPageState();
}
class _MyChildPageState extends State<MyChildPage> {
@override
Widget build(BuildContext context) {
return Navigator(
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/child_1':
return MaterialPageRoute(builder: (_) => MyChildPage1());
case '/child_2':
return MaterialPageRoute(builder: (_) => MyChildPage2());
case '/':
default:
return MaterialPageRoute(builder: (_) => MyChildPageRoot());
}
});
}
}
当您转到 MyChildPage 时,初始路线已更改,并且您没有后退箭头。
解决方案是将所有路线放在一类中。在下面的示例中,我不使用 MyChildPage。我在路由中直接使用 MyChildPageRoot。
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested tutorial',
theme: ThemeData(
primarySwatch: Colors.orange,
),
initialRoute: '/',
routes: {
'/': (_) => MyHomePage(),
'/sub_page': (_) => MySubPage(),
'/child': (_) => MyChildPageRoot(),
'/child_1': (_) => MyChildPage1(),
'/child_2':(_) => MyChildPage2(),
});
}
}
您无法使用嵌套导航器退出 MyChildPage,因为嵌套导航器仅在 MyChildPage 中创建。
您可以将父导航器传递到 MyChildPage 中,如下所示:
class MyChildPage extends StatefulWidget {
const MyChildPage({super.key, required this.parentNavigator, ...});
final NavigatorState parentNavigator;
然后,在 MyChildPage 的 AppBar 中添加一个前导后退按钮,如下所示:
appBar: AppBar(
...
leading: BackButton(
onPressed: () {
widget.parentNavigator.pop();
},
),
...
),
当按下 MyChildPage 应用栏中的后退按钮时,将调用父导航器弹出 MyChildPage,引导您返回进入 MyChildPage 之前的上一页。