路由的定义

路由(Route)在移动开发中通常指页面(Page),这跟web开发中单页应用的Route概念意义是相同的。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。

根据Flutter的文档,routes的灵感来源于reactjs,routes可以翻译为路由,可以看到这种routes的思路在目前的设计中彼此借鉴,routes的思路不仅在前端流行,比如在vue、reactjs、Angular中用到,而且在后端应用中也非常成熟。

  1. 我们先实现两个页面router_base_page.dart和router_base_next_page.dart 代码如下:

router_base_page.dart

import 'package:flutter/material.dart';
import 'router_base_next_page.dart';
class RouterBasePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text('基本的路由一'),
        ),
        body: Center(
            child: Padding(
                child: Column(
                    children: <Widget>[
                        Text('这一个基本的路由页面'),
                        SizedBox(height: 20.0,),
                        RaisedButton(
                            onPressed: (){
                                Navigator.of(context).push(MaterialPageRoute(builder: (context){
                                    return RouterBaseNextPage();
                                }));
                            },
                            child: Text('到下个页面'),
                        ),
                    ],
                ),
                padding: EdgeInsets.all(20.0),
            ),
        ),
    );
  }
}

router_base_next_page.dart

import 'package:flutter/material.dart';

class RouterBaseNextPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text('基本的路由页面的跳转页面'),
        ),
        body: Center(
            child: Padding(
                padding: EdgeInsets.all(20.0),
                child: Column(
                    children: <Widget>[
                        Text('基本的路由页面的跳转页面'),
                        SizedBox(height: 20.0),
                        RaisedButton(
                            child: Text('返回上一页'),
                            onPressed: (){
                                Navigator.of(context).pop();
                            },
                        ),
                    ],
                ),
            )
        ),
    );
  }
}

以上就是Flutter中最基本的路由!

MaterialPageRoute

MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。MaterialPageRoute 是Material组件库的一个Widget,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画:

  • 对于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,新页面将会从屏幕底部滑入(而不是水平方向)。

如果想自定义路由切换动画,可以自己继承PageRoute来实现。

Navigator.push

Navigator具体翻译为导航、跳转。为了显得简单易懂,这里不用指针解释push, 我这里用另外一种简单的方法来解释,在javascript中用push() 方法可向数组的末尾添加一个或多个元素,这就简单易懂了,就是追加一个页面来显示(NextPage)。

context

代表上下文,也就是类似windows中的句柄,指的是当前的这个页面窗口。

Navigator.pop(context)

pop在javascript中用于删除数组的最末一个元素,这就明白了,就是删除当前页面返回到Navigator中的前一个页面。

命名路由

所谓命名路由(Named Route)即给路由起一个名字,然后可以通过路由名字直接打开新的路由。这为路由管理带来了一种直观、简单的方式。

路由表

要想使用命名路由,我们必须先提供并注册一个路由表(routing table),这样应用程序才知道哪个名称与哪个路由Widget对应。路由表的定义如下:

Map<String, WidgetBuilder> routes;

它是一个Map, key 为路由的名称,是个字符串;value是个builder回调函数,用于生成相应的路由Widget。我们在通过路由名称入栈新路由时,应用会根据路由名称在路由表中找到对应的WidgetBuilder回调函数,然后调用该回调函数生成路由widget并返回。

怎么使用命名路由

我们需要先注册路由表后,我们的Flutter应用才能正确处理命名路由的跳转。注册方式很简单,我们再次新页面router_name.dart来代替app.dart,然后在RouterName类的build方法中找到MaterialApp,添加routes属性,来完成路由表的注册,代码如下:

import 'package:flutter/material.dart';
import 'package:flutter_rp/app/pages/router_name_base_page.dart';
import 'package:flutter_rp/app/pages/router_base_next_page.dart';
class RouterName extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: '命名路由',
        home: RouterNameBasePage(),
        routes: {
            'router_name_base_page': (context) => RouterBaseNextPage()
        },
    );
  }
}

再新建页面router_base_next_page.dart来完成命名路由的跳转

import 'package:flutter/material.dart';
class RouterNameBasePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text('命名路由基本页'),
        ),
        body: Center(
            child: Padding(
                child: Column(
                    children: <Widget>[
                        Text('这一个基本的路由页面'),
                        SizedBox(height: 20.0,),
                        RaisedButton(
                            onPressed: (){
                                Navigator.pushNamed(context, 'router_name_base_page');
                            },
                            child: Text('到下个页面'),
                        ),
                    ],
                ),
                padding: EdgeInsets.all(20.0),
            ),
        ),
    );
  }
}

命名路由的最大优点是直观,我们可以通过语义化的字符串来管理路由。

比如上面的

routes: {
    'router_name_base_page': (context) => RouterBaseNextPage()
},

很容易能看到我要跳转到哪个页面。

但其有一个明显的缺点,就是不能直接传递路由参数!

总结起来就是:

  • 命名路由简明并且系统,但是不能传参。
  • 在页面中单独构建路由可以传参,但比较繁琐。

所以fluro应运而生,fluro简化了Flutter的路由开发,也是目前Flutter生态中最成熟的路由框架,当然还存在一些缺陷,例如目前只支持传递字符串,不能传递中文等。

GitHub地址:https://github.com/theyakka/fluro

Fluro 的配置很简单,大概分如下的步骤:

  1. 创建一个路由的Handler对象。

Handler其实就是每个路由的规则,编写handler就是配置路由规则,比如我们要传递参数,参数的值是什么,这些都需要在Handler中完成。

例如:

import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import 'package:flutter_rp/app/pages/index_page.dart';
import 'package:flutter_rp/app/pages/article_detail_page.dart';
// app的首页
Handler indexHandler = Handler(
    handlerFunc: (BuildContext context, Map<String, List<String>> params) {
        return IndexPage();
    },
);

// 文章详情
Handler articleDetailHandler = Handler(
    handlerFunc: (BuildContext context, Map<String, List<String>> params){
        print(params);
        return ArticleDetailPage(id: params['id'].first,);
    },
);
  1. 创建路由表

就是为了建立路由中的路径和Handler之间的关系 例如:

import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import 'handler_router.dart';

class Routes{
    static const String index = '/';
    static const String articleDetail = 'article/detail/:id';
    static void configureRoutes(Router router){
        router.notFoundHandler = Handler(
            handlerFunc: (BuildContext context, Map<String, List<String>> params) {
                return Center(
                    child: Text('Not found'),
                );
            },
        );
        router.define(index, handler: indexHandler);
        router.define(articleDetail, handler: articleDetailHandler);
    }
}

这到里,基本上就把Fluro的路由配置好了,虽然配置的步骤然稍显复杂,但是跟层次和条理化,扩展性也很强,也易于管理路由。

如何使用!

  1. 定义一个全局的类

如果想使用Fluro,我们就必须在全局注入一个 Router 实例,但是在实际的项目中,我们会有许多实例需要在顶层注册,然后在全局注入使用,包括但不限于 fluro 的 router,http,database 等等!所以我们要使用一个全局的类来管理全局注入的对象!

例如:

import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';

class Application {
  static Dio dio; // 全局网络
  static Router router; // 全局路由
  static Database db; // 全局数据库
}
  1. 将 Router 注册到 MaterialApp 的 onGenerateRoute 中
import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import 'package:flutter_rp/app/application.dart';
import 'package:flutter_rp/app/routers/routes.dart';

class App extends StatelessWidget {
    App({Key key}) : super(key: key){
        /// 全局注入Router
        final router = Router();
        Routes.configureRoutes(router);
        Application.router = router;
        /// 全局注入Db等等。。。
    }
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'App',
            theme: ThemeData(
                backgroundColor: Colors.white,
            ),
            home: HomePage(),
            onGenerateRoute: Application.router.generator,
        );
    }
}

在需要路由跳转的页面:

import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import 'package:flutter_rp/app/application.dart';

......
onPressed: (){
    Application.router.navigateTo(context, 'article/detail/100', transition: TransitionType.fadeIn);
}
......