Flutter路由使用指北

作者: A_si | 来源:发表于2020-05-22 01:54 被阅读0次

    路由管理

    FLutter中的路由,和原生组件化的路由一样,就是页面之间的跳转,也可以称之为导航。app维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。

    MaterialPageRoute

    MaterialPageRoute是一种模态路由,可以针对不同平台自适应的过渡动画替换整个屏幕页面:

    对于Android,打开新页面时,新页面从屏幕底部导入到顶部。关闭页面的时候,会从顶部滑动到底部消失。

    在iOS上,页面从右侧滑入并反向退出。

    下面我们介绍一下MaterialPageRoute 构造函数的各个参数的意义:

      MaterialPageRoute({
        WidgetBuilder builder,
        RouteSettings settings,
        bool maintainState = true,
        bool fullscreenDialog = false,
      })
    

    builder 是一个WidgetBuilder类型的回调函数,它的作用是构建路由页面的具体内容,返回值是一个widget。我们通常要实现此回调,返回新路由的实例。
    settings 包含路由的配置信息,如路由名称、是否初始路由(首页)。
    maintainState:默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为false。
    fullscreenDialog表示新的路由页面是否是一个全屏的模态对话框,在iOS中,如果fullscreenDialog为true,新页面将会从屏幕底部滑入(而不是水平方向)

    基本使用

    Flutter为我们提供了导航器Navigator。参数传入当前的BuildContext和要导航的页面即可。

    1. 调用Navigator.push导航到第二个页面
       Navigator.push(
                 context, new MaterialPageRoute(builder:       (context) => Page2()));
      
    1. 调用Navigator.pop返回前一个页面
           Navigator.pop(context, result);
      
      
    1. 关闭页面后获取结果
      有时候我们需要上个页面关闭时传递一个返回值,幸运的是,Navigator的调用方法都是Future,因此我们可以等待它们的结果:

      3.1. 等待Navigator运行
      3.2. 将返回值传递给Navigator.pop函数
      3.3. 等待完成后,获取返回值

      在page1中,导航到page2,并且await到page2传递返回值并pop,根据返回值弹出不同的对话框:

      onPressed: () async {
            var navigationResult = await Navigator.push(
                context, new MaterialPageRoute(builder: (context) => Page2()));
      
            if (navigationResult == 'from_back') {
              showDialog(
                  context: context,
                  builder: (context) => AlertDialog(
                        title: Text('Navigation from back'),
                      ));
            } else if (navigationResult == 'from_button') {
              showDialog(
                  context: context,
                  builder: (context) => AlertDialog(
                        title: Text('Navigation from button'),
                      ));
            }
          },
      

      在page2中传递返回值并返回:

      Navigator.pop(context, 'from_button');
      
    2. 拦截返回键
      如果不想点击返回键关闭当前页面,可以使用WillPopScope小部件,用它放在最外层包括住脚手架。并向onWillPop返回false。false告诉系统当前页面不处理返回。

           @override
           Widget build(BuildContext context) {
             return WillPopScope(
               onWillPop: () => Future.value(false),
               child: Scaffold(
                 body: Container(
                   child: Center(
                     child: Text('Page 2',
                         style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold)),
                   ),
                 ),
               ),
             );
           }
    
    如果想自定义处理返回键,可以在return false 之前自己处理,比如关闭    当前页面并传递返回值:
    
          WillPopScope(
                onWillPop: () async {
                    // You can await in the calling widget for my_value and handle when complete.
                    Navigator.pop(context, 'my_value');
                    return false;
                  },
                  ...
          );
    

    命名路由

    基本使用

    上面代码是在没个需要导航的地方声明路由,不能复用,我们可以先给路由起一个名字,再注册路由表,然后就可以通过路由名字直接打开新的路由了,这为路由管理带来了一种直观、简单的方式,并且可以复用。

    MaterialApp的routes属性,既是注册路由表用的,它对应一个Map<String, WidgetBuilder>。

    起名:

      static const String page1 = "/page1";
      static const String page2 = "/page2";
    

    声明路由表:

      Map<String, WidgetBuilder> routes = {
        page1: (context) => Page1(),
        page2: (context) => Page2(),
      };
    

    注册路由表:

     @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          routes: routes,
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          home: Page1(),
        );
      }
    

    然后在需要路由的地方使用命名路由调用:

    Navigator.pushNamed(context, page2)
    

    传递参数

    给page3起名:

        page3: (context) => Page3(),
    
    

    打开路由时候传递参数:

                  Navigator.of(context).pushNamed(page3, arguments: "hi");
    
    

    page3中接收参数:

    class Page3 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        //获取路由参数
        var args = ModalRoute.of(context).settings.arguments;
        return Scaffold(
          body: Container(
            child: Center(
              child: Text('Page 3的参数是$args',
                  style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold)),
            ),
          ),
        );
      }
    }
    
    

    构造函数传参

    上面我们明明给page3传递了参数,但是并非传递到构造函数上。我们看构造函数,并不知道传递了什么参数,必须去看路由,并不是很好的做法。那怎么给构造函数传参呢?

    起名:

    const String page4 = "/page4";
    
    

    注册路由:

        page4: (context) => Page4(text: ModalRoute.of(context).settings.arguments),
    
    

    打开路由时传递参数:

                  Navigator.of(context).pushNamed(page4, arguments: "hello");
    
    

    动态路由

    MaterialApp还为我们提供了一个onGenerateRoute参数,未在路由表里注册的路由,会在这里寻找。RouteFactory有一个RouteSettings参数,并返回一个Route<dynamic>。这是我们将用来执行所有路由的功能。

    Route<dynamic> Function(RouteSettings settings)
    
    

    我们可以这样使用:
    先声明路由表:

    Route<dynamic> generateRoute(RouteSettings settings) {
        switch (settings.name) {
            case page5:
                return MaterialPageRoute(builder: (context) => Page5());
            case page6:
                return MaterialPageRoute(builder: (context) => Page6());
            default:
                return MaterialPageRoute(builder: (context) => Page1());
        }
    

    注册:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          routes: routes,
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          onGenerateRoute: generateRoute,
          home: Page1(),
        );
      }
    

    打开路由:

                  Navigator.of(context).pushNamed(page5);
    
    

    动态路由传递参数

    上面说了,settings可以拿到参数,我们当然就可以传递参数了:

    Route<dynamic> generateRoute(RouteSettings settings) {
        print('====${settings.name}');
        switch (settings.name) {
            case page5:
                return MaterialPageRoute(builder: (context) => Page5());
            case page6:
                return MaterialPageRoute(builder: (context) => Page6(text: settings.arguments,));
            default:
                return MaterialPageRoute(builder: (context) => Page1());
        }
    }
    

    使用:

                  Navigator.of(context).pushNamed(page6, arguments: "world");
    
    

    so easy。

    处理未定义的路线

    有两种处理未定义路由的方法。

    1. 利用generateRoute,找不到路由名的返回默认路由
    Route<dynamic> generateRoute(RouteSettings settings) {
        print('====${settings.name}');
        switch (settings.name) {
            case page5:
                return MaterialPageRoute(builder: (context) => Page5());
            case page6:
                return MaterialPageRoute(builder: (context) => Page6(text: settings.arguments,));
            default:
                return MaterialPageRoute(builder: (context) => NotFindPage());
        }
    }
    
    1. 利用onUnknownRoute返回默认路由
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Flutter Demo',
            routes: routes,
            theme: ThemeData(
              primarySwatch: Colors.blue,
              visualDensity: VisualDensity.adaptivePlatformDensity,
            ),
            onGenerateRoute: generateRoute,
            onUnknownRoute: (settings) =>
                MaterialPageRoute(builder: (context) => NotFindPage()));
      }
    

    初始路由

    打开应用第一屏的路由,也有2种方式,

    1. 可以设置initialRoute,指定路由表里注册的路由名。
    2. 可以设置home,对应的page。
      initialRoute会覆盖home。
     Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Flutter Demo',
            routes: routes,
            theme: ThemeData(
              primarySwatch: Colors.blue,
              visualDensity: VisualDensity.adaptivePlatformDensity,
            ),
            initialRoute: root,
            home: Page2(),
            onGenerateRoute: generateRoute,
            onUnknownRoute: (settings) =>
                MaterialPageRoute(builder: (context) => NotFindPage()));
      }
    

    不使用BuildContext的路由导航

    很多情况是,我们已将UI代码从业务逻辑中分离出来(类似于MVVM架构)。viewModel应处理所有逻辑,视图应仅调用模型上的函数,然后在需要时使用新状态重建自身。

    我们知道Navigator需要BuildContext的参数,我们在进行实际业务逻辑决策的位置进行导航,而不是在widget里调用路由,如果在viewModel里导航,就要传入context吗?下面实现不要context的导航。

    为了遵守MVVM原则,我们将把Navigation功能移动到可以从viewModel调用的服务中。在lib下创建一个名为services的新文件夹,并在其中创建一个名为navigation_service.dart的新文件。

    先实现单利模式:

    class NavigationService {
      factory NavigationService.getInstance() => _getInstance();
    
      NavigationService._internal();
    
      static NavigationService _instance;
    
      static NavigationService _getInstance() {
        if (_instance == null) {
          _instance = new NavigationService._internal();
        }
        return _instance;
      }
      }
    
    然后利用navigatorKey实现:
    
     final GlobalKey<NavigatorState> navigatorKey =
          new GlobalKey<NavigatorState>();
    
      Future<dynamic> navigateTo(String routeName) {
        return navigatorKey.currentState.pushNamed(routeName);
      }
    
      void goBack() {
        return navigatorKey.currentState.pop();
      }
    

    我们将NavigationService与应用程序链接的方式,通过navigatorKey提供给MaterialApp。转到main.dart文件并设置navigatorKey:

     MaterialApp(
            title: 'Flutter Demo',
            navigatorKey: NavigationService().navigatorKey,
            ...
            )
    

    然后写一个viewModel,尝试导航:

    class ViewModel {
      final NavigationService _navigationService = NavigationService();
    
      Future goPage1() async{
        /// 模拟请求数据后调到首页
          await Future.delayed(Duration(seconds: 1));
          _navigationService.navigateTo(page1);
      }
    
    }
    

    在page6里使用viewModel导航:

      onPressed: () {
              viewModel.goPage1();
            },
    

    现在,将View文件的职责带回到了“显示UI”并将用户操作传递给模型,而不是“显示UI”将用户操作传递给模型并进行导航。更符合MVVM职责的划分。

    这样做的好处是,随着导航逻辑的扩展,我们的UI将保持不变,并且模型将承载所有逻辑/状态管理。

    代码链接

    相关文章

      网友评论

        本文标题:Flutter路由使用指北

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