Flutter第五章(各种Route)

版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!

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

欢迎来到本章节,上一章节我们讲了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 {
    @override
    Widget build(BuildContext context) {
      return Center(
        child: Column(
          children: [

            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 {
    @override
    Widget build(BuildContext context) {
      return Center(
        child: Column(
          children: [
            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: [
                // 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 {
    @override
    Widget build(BuildContext context) {
      return Center(
        child: Column(
          children: [
            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))