【问题标题】:Flutter: nested routes scaffold not showing back navigationFlutter:嵌套路由脚手架不显示后退导航
【发布时间】:2021-09-12 11:23:46
【问题描述】:

我想利用 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


    【解决方案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(),
        });
      }
    }
    

    【讨论】:

    • 嗯,使用多个导航器的目的是让我的代码更具可读性和可管理性。我知道我可以将所有路线推入MaterialApp,但这不是我想要实现的。我认为通过在_MyChildPageState 中设置initalRoute 来将这个嵌套导航器的初始路由设置为/child/,就像在URL 中一样。
    • 首先,如果你想要更多的可读性,将所有路由移动到外部文件。其次,如果您想要特定于网页(如网页)的 url,则必须以其他方式创建导航。您可以在 YouTube 上以“flutter web navigation”为口号找到更多信息。第三,您始终可以将您的路线嵌套在“/child/child_1”之类的字符串中。我相信其中一种解决方案将帮助您完成任务。
    • 谢谢。我会考虑的,我来自网络世界,所以也许我的假设不会在移动设备上 1:1 映射。然而,我确实看到人们使用多个 Navigator 对象将代码块从我喜欢的应用程序的不同部分中分离出来。你是对的,也许在不同的文件中定义路由会有助于提高可读性。
    【解决方案2】:

    所以在网上研究了一番之后,我想我已经有了一些解决方案。

    AppBar 中未显示后退箭头的第一个问题是由于为每个路由(MyChildPageRootMyChildPage1MyChildPage2)定义了单独的 Scaffold 引起的。在_MyChildPageState 中创建单个脚手架并在其body 中渲染Navigator 解决了这个问题。

    虽然这可能不是最灵活的解决方案(您可能需要为每条路线使用不同的脚手架),但我还没有想出不同的方法。

    修复此问题后,后退导航有效,但子路由将返回到应用程序路由(初始屏幕),而不是返回到MyChildPageRoot

    我设法通过使用WillPopScope 小部件将Scaffold 包装在_MyChildPageState 中并为嵌套的导航器创建单独的导航键来解决此问题:

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

    【讨论】:

      猜你喜欢
      • 2022-11-06
      • 2011-08-16
      • 2023-01-03
      • 1970-01-01
      • 2019-10-18
      • 1970-01-01
      • 2019-08-02
      • 2017-11-06
      • 2020-04-21
      相关资源
      最近更新 更多