Flutter第五章(各种Route)

作者: 一巴掌拍出两坨脂肪 | 来源:发表于2021-04-17 17:15 被阅读0次
    版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!

    情感语录:别说什么火克木,水克土;你只有努力,只有成功了才能克天、克地、克人生。

    欢迎来到本章节,上一章节我们讲了StatelessWidgetStatefulWidget组件还记得吗?知识点回顾 戳这里 Flutter基础第四章

    本章节主要讲解路由,Flutter 中的路由通俗的讲就是页面跳转。在Android原生中是通过意图Intent来进行跳转的;在 Flutter 中通过 Navigator 组件管理路由导航;并提供了管理堆栈的方法。如:Navigator.of(context).push(跳转到目标页面) 和 Navigator.of(context).pop(退出目标页面,既返回到上一个页面)

    本章简要:

    1、普通路由、普通路由传值、命名路由、命名路由传值

    2、 pushReplacementNamed路由替换 、pushNamedAndRemoveUntil返回到根路由

    Flutter 中给我们提供了两种配置路由跳转的方式:普通路由 和 命名路由

    一、普通路由和传值

    1、普通路由实例:首先我们准备两个页面,一个是主页,主页上有按钮,点击按钮然后跳转到第二个界面。新建一个SecondPage.dart页面用于验证主页是否能跳转过来。

       import 'package:flutter/material.dart';
    
       class SecondPage extends StatelessWidget {
    
        String value;
    
        //构造函数接收一个可选命名参数
        SecondPage({this.value = ""});
    
    
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(
              title: Text("第二个界面"),
            ),
            body: Center(
              child: Text("这是第二个界面${this.value}"),
            ),
          );
        }
      }
    

    2、在主页中通过普通路由:Navigator.of(context).push(MaterialPageRoute(builder: (context) => 目标页面 )); 进行跳转

      import 'package:flutter/material.dart';
    
      import 'page/SecondPage.dart';
    
      void main() => runApp(MyApp());
    
      class MyApp extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return new MaterialApp(
              home: new Scaffold(
                  appBar: AppBar(
                    title: Text("呆萌"),
                  ),
                  body: HomePage()));
        }
      }
    
      class HomePage extends StatefulWidget {
        HomePage({Key key}) : super(key: key);
    
        _HomePageState createState() => _HomePageState();
      }
    
      class _HomePageState extends State<HomePage> {
        @override
        Widget build(BuildContext context) {
          return Center(
            child: Column(
              children: <Widget>[
    
                RaisedButton(
                    child: Text("跳转到第二个界面"),
                    onPressed: () {
                      //路由跳转
                      Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondPage()));
                    },
                    color: Theme.of(context).accentColor,
                    textTheme: ButtonTextTheme.primary),
                SizedBox(
                  height: 10,
                ),
                RaisedButton(
                    child: Text("传值跳转到第二个界面"),
                    onPressed: () {
                      // 通过构造函数传值,上面的写法也可以换成这种方式 去掉of
                      Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage(value: "----->传值跳转",)),
                      );
                    },
                    color: Theme.of(context).accentColor,
                    textTheme: ButtonTextTheme.primary),
              ],
            ),
          );
        }
      }
    

    MaterialPageRoute 创建路由,它是一种模态路由,可以通过平台自适应的过渡效果来切换屏幕。默认情况下,当一个模态路由被另一个替换时,上一个路由将保留在内存中,如果想释放所有资源,可以将 maintainState 设置为 false,下面我们来看上面代码效果:

    基本路由和传值.gif

    可以看到两种跳转方式都没问题,且在跳转时通过构造函数成功将值(----->传值跳转)传入到了第二个界面;细心的你有没有发现一个神奇的地方,在第二个界面我们并没有加返回按钮,怎么自己就有出来了一个呢?其实Flutter中会检测到你是通过路由跳页面的情况,Scaffold 控件会自动在 AppBar 上添加一个返回按钮,点击该按钮会调用 Navigator.pop 即退出该页面;如果在页面的其他地方也有退出按钮,在添加事件后,手动调用也是一样可以实现的。

    二、命名路由和传值

    1、命名路由跳转

    命名路由应该是开发中用的最多的一种方式,使用起来虽然比普通路由相对复杂一点,但它可以做到统一管理,便于维护。路由名称通常使用路径结构:/xxx/xxx/目标页,主页默认为 '/' (启动第一个加载的页面)。既:Navigator.pushNamed(context,/xxx/xxx/目标页)方式调用进行跳转 。创建 MaterialApp 时可以指定 routes 参数,该参数是一个映射路由名称和构造器的 Map。MaterialApp 使用此映射为导航器的 onGenerateRoute 回调参数提供路由。下面我们来将上面例子简单修改成命名路由。

      import 'package:flutter/material.dart';
      import 'page/SecondPage.dart';
    
      void main() => runApp(MyApp());
    
      class MyApp extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return new MaterialApp(
              home: new Scaffold(
                  appBar: AppBar(
                    title: Text("呆萌"),
                  ),
                  body: HomePage()),
    
              //在MaterialApp 中配置路由
              routes: {
                '/SecondPage': (context) => SecondPage(),
              });
        }
      }
    
      class HomePage extends StatefulWidget {
        HomePage({Key key}) : super(key: key);
    
        _HomePageState createState() => _HomePageState();
      }
    
      class _HomePageState extends State<HomePage> {
        @override
        Widget build(BuildContext context) {
          return Center(
            child: Column(
              children: <Widget>[
                RaisedButton(
                    child: Text("跳转到第二个界面"),
                    onPressed: () {
                      //命名路由跳转
                      Navigator.pushNamed(context, '/SecondPage');
                    },
                    color: Theme.of(context).accentColor,
                    textTheme: ButtonTextTheme.primary),
              ],
            ),
          );
        }
      }
    

    第二页面也稍做修改:

      import 'package:flutter/material.dart';
    
      class SecondPage extends StatelessWidget {
    
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            //这里也添加一个返回按钮。
            floatingActionButton: FloatingActionButton(
              child: Text('返回'),
              onPressed: (){
                // 返回上一个页面
                Navigator.of(context).pop();
              },
            ),
            appBar: AppBar(
              title: Text("第二个界面"),
            ),
            body: Center(
              child: Text("这是第二个界面"),
            ),
          );
        }
      }
    
    命名路由跳转.gif

    通过命名路由也能实现跳转。但这里有两个注意点需要记住:

    1、必须在MaterialApp中进行路由管理配置。

    2、路由管理配置的目标页面名称必须和跳转时写的一致,否则不能跳转。既routes下的'/SecondPage' 必须和Navigator.pushNamed中填写的一致。这里建议可以使用全局常量进行配置优化。

    2、命名路由传值

    细心的你可能发现一个问题,命名路由是通过Navigator.pushNamed方式跳转,于是这里再也没有构造函数进行利用传值,那怎么办?没办法,先看下官方文档,官方文档有这么一段代码:

        import 'package:flutter/material.dart';
    
        void main() => runApp(MyApp());
    
        class MyApp extends StatelessWidget {
          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              // Provide a function to handle named routes. Use this function to
              // identify the named route being pushed, and create the correct
              // Screen.
              onGenerateRoute: (settings) {
                // If you push the PassArguments route
                if (settings.name == PassArgumentsScreen.routeName) {
                  // Cast the arguments to the correct type: ScreenArguments.
                  final ScreenArguments args = settings.arguments;
    
                  // Then, extract the required data from the arguments and
                  // pass the data to the correct screen.
                  return MaterialPageRoute(
                    builder: (context) {
                      return PassArgumentsScreen(
                        title: args.title,
                        message: args.message,
                      );
                    },
                  );
                }
              },
              title: 'Navigation with Arguments',
              home: HomeScreen(),
            );
          }
        }
    
        class HomeScreen extends StatelessWidget {
          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(
                title: Text('Home Screen'),
              ),
              body: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    // A button that navigates to a named route that. The named route
                    // extracts the arguments by itself.
                    RaisedButton(
                      child: Text("Navigate to screen that extracts arguments"),
                      onPressed: () {
                        // When the user taps the button, navigate to the specific route
                        // and provide the arguments as part of the RouteSettings.
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => ExtractArgumentsScreen(),
                            // Pass the arguments as part of the RouteSettings. The
                            // ExtractArgumentScreen reads the arguments from these
                            // settings.
                            settings: RouteSettings(
                              arguments: ScreenArguments(
                                'Extract Arguments Screen',
                                'This message is extracted in the build method.',
                              ),
                            ),
                          ),
                        );
                      },
                    ),
                    // A button that navigates to a named route. For this route, extract
                    // the arguments in the onGenerateRoute function and pass them
                    // to the screen.
                    RaisedButton(
                      child: Text("Navigate to a named that accepts arguments"),
                      onPressed: () {
                        // When the user taps the button, navigate to a named route
                        // and provide the arguments as an optional parameter.
                        Navigator.pushNamed(
                          context,
                          PassArgumentsScreen.routeName,
                          arguments: ScreenArguments(
                            'Accept Arguments Screen',
                            'This message is extracted in the onGenerateRoute function.',
                          ),
                        );
                      },
                    ),
                  ],
                ),
              ),
            );
          }
        }
    
        // A Widget that extracts the necessary arguments from the ModalRoute.
        class ExtractArgumentsScreen extends StatelessWidget {
          static const routeName = '/extractArguments';
    
          @override
          Widget build(BuildContext context) {
            // Extract the arguments from the current ModalRoute settings and cast
            // them as ScreenArguments.
            final ScreenArguments args = ModalRoute.of(context).settings.arguments;
    
            return Scaffold(
              appBar: AppBar(
                title: Text(args.title),
              ),
              body: Center(
                child: Text(args.message),
              ),
            );
          }
        }
    
        // A Widget that accepts the necessary arguments via the constructor.
        class PassArgumentsScreen extends StatelessWidget {
          static const routeName = '/passArguments';
    
          final String title;
          final String message;
    
          // This Widget accepts the arguments as constructor parameters. It does not
          // extract the arguments from the ModalRoute.
          //
          // The arguments are extracted by the onGenerateRoute function provided to the
          // MaterialApp widget.
          const PassArgumentsScreen({
            Key key,
            @required this.title,
            @required this.message,
          }) : super(key: key);
    
          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(
                title: Text(title),
              ),
              body: Center(
                child: Text(message),
              ),
            );
          }
        }
    
        // You can pass any object to the arguments parameter. In this example,
        // create a class that contains both a customizable title and message.
        class ScreenArguments {
          final String title;
          final String message;
    
          ScreenArguments(this.title, this.message);
        }
    

    官方文档这段代码看起来有点长哈,你仔细看其实发现不难理解,首先是在HomeScreen 中通过arguments指定了要传的值,案例中是封装在ScreenArguments 对象中。然后在MaterialApp 中通过onGenerateRoute()进行参数提取,然后路由名进行判断,如果是指定的路由名通过settings将传入的值进行提取,接着MaterialPageRoute通过构建把值转入。

    你可能又有问题要问了,上面代码确实不难,但有一个问题,如果有很多个页面我想通过命名路由传值,是不是都要去做分别判断然后再处理不同类型的值提取呢?是否可以对官方文档这段代码进行优化呢?我想要的是一次处理就好! 咦....这个问题过分了啊!!我想想, 嗯.................呜....... 当然还是可以啦。下面我们来看看吧咳咳......重点:

    为了方便统一管理,我们将路由单独抽离出来,先新建一个route文件夹(名字随意见名知意即可),然后新建Routes.dart文件,然后写这么一段代码:

      import 'package:flutter/material.dart';
      import 'package:flutter_learn/page/SecondPage.dart';
      
      
      //配置路由
      final routes={
      
         //如有多个,请在这里添加即可。
        '/SecondPage':(context,{arguments})=>SecondPage(arguments:arguments),
      
      };
      
      //优化后
      // ignore: strong_mode_top_level_function_literal_block
      var onGenerateRoute=(RouteSettings settings) {
        // 统一处理
        final String name = settings.name;
      
        final Function pageContentBuilder = routes[name];
      
        if (pageContentBuilder != null) {
          if (settings.arguments != null) {
            final Route route = MaterialPageRoute(
                builder: (context) =>
                    pageContentBuilder(context, arguments: settings.arguments));
            return route;
          }else{
            final Route route = MaterialPageRoute(
                builder: (context) =>
                    pageContentBuilder(context));
            return route;
          }
        }
      };
    

    咦.........看不懂?没关系,这段代码拿过去用即可,感觉很牛逼的样,不试试怎么知道呢? 下面我们来简单修改下主页和第二个页面:

      import 'package:flutter/material.dart';
      import 'package:flutter_learn/route/Routes.dart';
    
      void main() => runApp(MyApp());
    
      class MyApp extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return new MaterialApp(
              home: new Scaffold(
                  appBar: AppBar(
                    title: Text("呆萌"),
                  ),
                  body: HomePage()
              ),
    
               //initialRoute: '/', //初始化的时候加载的路由
               onGenerateRoute: onGenerateRoute);
        }
      }
    
      class HomePage extends StatefulWidget {
        HomePage({Key key}) : super(key: key);
    
        _HomePageState createState() => _HomePageState();
      }
    
      class _HomePageState extends State<HomePage> {
        @override
        Widget build(BuildContext context) {
          return Center(
            child: Column(
              children: <Widget>[
                RaisedButton(
                    child: Text("跳转到第二个界面"),
                    onPressed: () {
                      //命名路由跳转 并传值
                      Navigator.pushNamed(context, '/SecondPage',arguments: {
                        "key":"哎呦,你来啦O(∩_∩)O"
                      });
                    },
                    color: Theme.of(context).accentColor,
                    textTheme: ButtonTextTheme.primary),
              ],
            ),
          );
        }
      }
    

    上面代码中我们在MaterialApp中引用了抽离出去的onGenerateRoute ,然后在点击按钮后通过Navigator.pushNamed传入了一个arguments参数,arguments其实就是个Map类型,通过key,value传值即可。

    第二个页面也需要处理下,我们在构造函数中接受下这个arguments 参数。

      import 'package:flutter/material.dart';
    
      class SecondPage extends StatelessWidget {
    
        final arguments;
    
        SecondPage({this.arguments});
    
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            //这里也添加一个返回按钮。
            floatingActionButton: FloatingActionButton(
              child: Text('返回'),
              onPressed: (){
                // 返回上一个页面
                Navigator.of(context).pop();
              },
            ),
            appBar: AppBar(
              title: Text("第二个界面"),
            ),
            body: Center(
              child: Text("这是第二个界面 传值内容:"+arguments["key"]),
            ),
          );
        }
      }
    

    好了,运行尝试下效果吧:

    命名路由传值.gif

    哈哈,看到了吧,成功的将值哎呦,你来啦O(∩_∩)O传入到了第二个界面。牛逼,手动 666.......... O(∩_∩)O

    三、pushReplacementNamed路由替换 、pushNamedAndRemoveUntil 返回到根路由

    上面我们讲了路由跳转,也提及和运用了使用路由返回到上一个界面Navigator.of(context).pop(); 或者 Navigator.pop(context); 如果有参数需要携带回去还可以使用Navigator.pop(context,"携带参数");

    3.1 pushReplacementNamed

    生活常常有这样一种场景,比如你打开某一个应用 首先进入了欢迎页 再然后进入了主页 当你点击主页返回按钮希望的是退出应用,而不是再次回到了欢迎页,此时 pushReplacementNamed 进行路由替换跳转就可以排上用场了,使用格式如下:

    Navigator.of(context).pushReplacementNamed('/主页');

    3.2 pushNamedAndRemoveUntil

    假设现在有A、B、C三个界面,当用户从A界面依次进入到了C界面,再C界面想直接回到A界面那该怎么办呢?此时pushNamedAndRemoveUntil路由跳转就可以排上用场了,使用格式如下:

            //返回根
            Navigator.of(context).pushAndRemoveUntil(
                new MaterialPageRoute(builder: (context) => A()),
                   (route) => route == null
             );
    

    在上面中讲到了可以通过pop 将值可以携带回上一个页面,那上一个页面怎么接收携带回来的这个值呢? 其实无论是在普通路由还是命名路由中我们都可以使用then函数进行接收。

           //命名路由跳转 并传值
          Navigator.pushNamed(context, '/SecondPage',arguments: {
           "key":"哎呦,你来啦O(∩_∩)O"
          }).then((value){  //接收回传回来的值
              print(value);
            });
    

    好了本章节的东西虽然不多也不是很难,但是在Flutter中极其重要,本章节的案例可能相对较少,尤其是第三大点所讲的东西并未给出演示效果和Demo。因为这个东西确实不难,稍微练习下即可,该章节的内容在后面章节中也会经常使用到,所以懒的练习的同学希望持续关注我哟,O(∩_∩)O 。谢谢大家观看,下章再会 !!!!

    实例源码地址: https://github.com/zhengzaihong/flutter_learn

    相关文章

      网友评论

        本文标题:Flutter第五章(各种Route)

        本文链接:https://www.haomeiwen.com/subject/bdassctx.html