Flutter:嵌套路由脚手架不显示后退导航

问题描述 投票:0回答:3

我想在 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
小部件中定义所有路由。

flutter routes nested-routes
3个回答
2
投票

经过一些网上研究,我想我已经找到了一些解决方案。

后退箭头未显示在

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


1
投票

问题出在初始路线上。您使用初始路线创建了两个课程:

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

0
投票

您无法使用嵌套导航器退出 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 之前的上一页。

© www.soinside.com 2019 - 2024. All rights reserved.