Skip to content

Flutter:路由管理

Published: at 00:00

无论是前端浏览器页面还是端上 App 路由管理是必不可少的,路由可以让页面间跳转更加流畅。因 Flutter 万物皆 Widget 的特定,路由的跳转其实也就是切换不同的 Widget,这和前端思想很相似(组件化开发),因此很好理解。接下来就看一下 Flutter 中组件是怎么切换的。

在前端中有路由的概念,例如 Vue,有 vue-router 这个库,在 vue-router 中,所有可跳转的组件都会在路由中注册,用 route-link 组件来匹配要展示的视图,最终显示在 route-view 组件所在的位置。而 Flutter 中使用NavigatorAPI 来控制路由的跳转。

在 Flutter 中也会维护一个路由的历史记录栈,通过Navigator.push()做路由的前进(往记录栈中存路由记录),通过Navigator.pop()做路由的后退(往记录栈中出路由记录),很像浏览器的 HistoryAPI。

Navigator.push()方法用于路由的前进,它接收两个参数:

  1. 第一个是当前组件的 context(Flutter 万物皆组件)
  2. 第二个是 MaterialPageRoute 类表示 Material 风格的路由切换动画

由于平台的不同,对于不同的平台路由切换动画是会有些区别的

下面来说一下MaterialPageRoute

MaterialPageRoute 类一共接收四个参数,其意义如下:

🌰: 有一个 HomePage 的 Widget

class HomePage extends StatefulWidget {
  final String title;
  HomePage({Key key, this.title}): super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page')
      ),
      body: Center(
        child: FlatButton(
          child: Text('open about page'),
          textColor: Colors.red,
          onPressed:() {
            //TODO 跳转的逻辑
          }
        )
      )
    )
  }
}

再写一个 AboutPage

class AboutPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('About Page')
      ),
      body: Center(
        child: Text('About Page Content')
      )
    )
  }
}

上面是两个页面的组件,我们要做的是从 HomePage 中点击按钮跳转到 AboutPage。下面来看一下按钮点击事件中的逻辑。

onPressed: () {
  Navigator.push(context, MaterialPageRoute(builder: () {
    return AboutPage()
  }))
}

此时点击 HomePage 中的按钮就会跳转到 AboutPage 页。

Navitagor.pop()用于路由的回退。我们在 AboutPage 中添加一个回退按钮

class AboutPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('About Page')
      ),
      body: Center(
        child: FlatButton(
          child: Text('回退'),
          textColor: Colors.blue,
          onPressed: () => Navigator.pop(context)
        )
      )
    )
  }
}

当在 AboutPage 页面点击回退按钮时,Flutter 就回退到了 HomePage 页面。

Navigator 类中的第一个参数为 context 的静态方法都会对应一个 Navigator 的实例,例如:

Navigator.push(context,MaterialPageRoute(builder: () {
  return AboutPage()
}))
//等价于
Navigator.of(context).push(MaterialPageRoute(builder: () {
  return AboutPage()
}))

Navigator.pop(context)
//等价于
Navigator.of(context).pop()

路由间传值

有页面间跳转,就必然少不了路由间传值,例如进入 App 首先进入一个活动列表页,点击活动列表页的一个活动进入该活动详情页,在详情页里就需要使用到活动 id 去请求该活动的数据,此时就需要从列表页点击活动进行路由跳转的时候带上活动 id。

那么在 Flutter 中怎么做到路由间传值的呢?

首先来说第一种方式:我们都知道每一个页面其实都是一个 Widget,严格来说都是一个类,可以在跳转的时候通过构造函数参数的形式传递进去。

🌰

class ActivityList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ActivityList')
      ),
      body: ListView(
        children: <Widget>[
          FlatButton(
            child: Text('活动1'),
            textColor: Colors.red,
            onPressed: () {
              Navigator.of(context).push(MaterialPageRoute(builder: () {
                return ActivityDetails(
                  activityId: 1
                )
              }))
            }
          )
        ]
      )
    )
  }
}

接下来写活动详情类

class ActivityDetails extends StatelessWidget {
  final num activityId;
  ActivityDetails({Key key, this.activityId}): super(key: key);
 @override
 Widget build(BuildContext build) {
   return Scaffold(
     appBar: AppBar(
       title: Text('活动详情页')
     ),
     body: Text('活动Id为$activityId')
   )
 }
}

Flutter 的 NavigatorAPI 不仅提供了前进可以带数据,还提供了返回带参数。当使用 Navigator.pop()返回的时候,也可以带数据到前一个视图中。

修改一下上面的组件。

//先修改ActivityDetails
class ActivityDetails extends StatelessWidget {
  final num activityId;
  ActivityDetails({Key key, this.activityId}): super(key: key);
 @override
 Widget build(BuildContext build) {
   return Scaffold(
     appBar: AppBar(
       title: Text('活动详情页')
     ),
     body:Column(
       children: <Widget>[
          Text('活动Id为$activityId'),
          FlatButton(
            child: Text('回退'),
            textColor: Colors.green,
            onPressed: () => Navigator.of(context).pop('我是回退的内容')
          )
       ]
     )
   )
 }
}
//修改ActivityList中的路由Wie可接受数据的
onPressed: async () {
  var result = await Navigator.of(context).push(MaterialPageRoute(builder: () {
    return ActivityDetails(
      activityId: 1
    );
  }));
  print('result:$result');
}

由于这个操作本身就是一个异步行为,所以在 ActivityList 中将 onPressed 改为异步的,Navigator.push()的返回值就是 Navigator.pop()中返回的值:我是回退的内容

命名路由

通过上面内容已经知道了怎么去做路由的前进与回退和路由间传值。但是我们发现一个问题,路由的前进是通过返回组件实例来做的,难道说所有的组件都要写在一个文件里面吗?又或者所有的文件间引入来引入去吗?这很显然是不行的。

好在 Flutter 提供命名路由的方案。命令路由就是说给每路由都起一个名字,在根组件中维护一个路由表,跳转匹配路由名称确定跳转到哪个页面下。

路由表是一个 key-value 的 Map 结构,如下:

Map routes = <String, WidgetBuilder> = {
  '/': HomePage(),
  'about': AboutPage()
}

router 的配置位置在 App Widget 中,在 MaterialApp 这个 Widget 中配置。

MaterialApp(
  title: 'Flutter Deom',
  theme: ThemeData(primarySwatch: Colors.red),
  initialRoute: '/', //名字为/的路由作为应用的home(首页)
  // 路由生成钩子
  onGenerateRoute: (RouteSettings settings) {
    return MaterialPageRoute(builder: (context) {
      String routeName = settings.name;
      print(routeName);
      return;
    });
  },
  // 注册路由表
  routes: routes
);

上面的 MaterialApp 去掉了 home,多了 initialRoute、onGenerateRoute、routes,这三个都是可命名路由相关的。

使用命名路由的方式,在点击跳转的时候就不可以使用 push 了,而是使用 pushNamed 方法,这个方法的第一个参数指定路由表的 key 值。例如:

Navigator.of(context).pushNamed('about');

命名路由的路由间传值

当使用命名路由的时候,由于在激活路由跳转的时候并不能操作路由 Widget 了,所以之前的一种传值就不能满足当前的写法。

Navigator.pushNamed()方法提供 arguments 的可选命名参数,arguments 就是需要转递到下一个路由的值。

在下一个路由中使用ModalRoute.of(context).settings.arguments就可以取到 arguments。

例如:

// 传值
Navigator.of(context).pushNamed('about', arguments: 'Hello world');

//取值
var result = ModalRoute.of(context).settings.arguments;
print('上一个路由传过来的值:$result');

如何管理路由

在写 Vue 和 React 的时候我们是把所有的页面放在 pages 目录下,创建一个 routes 目录,在这里做路由的统一管理。Flutter 也是一个 UI 库,其思想和前端框架并不相差太多,所以也可以使用这种工程目录结构。

我们来简单定义一个工程目录结构。

 /--
  |--pages
    - HomePage.dart
    - AboutPage.dart
  |--routes
    -index.dart
  - main.dart

在 index.dart 中定义路由表。

//引入Material风格Widget
import 'package:flutter/material.dart';
//引入Page下的Widget
import 'package:flutter_demo/pages/HomePage.dart';
import 'package:flutter_demo/pages/AboutPage.dart';

//定义路由表
Map routes = <String, WidgetBuilder> {
  '/': HomePage(),
  'about': AboutPage()
}

在 main.dart 中引入routes/index.dart,并将 routes 注册到 MaterialApp 中。

import 'package:flutter/material.dart';
import 'package:flutter_demo/routes/index.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Deom',
        theme: ThemeData(primarySwatch: Colors.red),
        initialRoute: '/', //名字为/的路由作为应用的home(首页)
        onGenerateRoute: (RouteSettings settings) {
          return MaterialPageRoute(builder: (context) {
            String routeName = settings.name;
            print(routeName);
            return;
          });
        },
        // 注册路由表
        routes: routes
      );
  }
}

这样在写 Flutter 项目的时候就和写前端项目的体验相同了。

总结

Flutter 对前端来说还是很友好的,无论是 dart 语言还是 Flutter 框架的整体思想上和前端框架有很多相似的地方,个人感觉接收起来还是比较容易的,毕竟思想大致相同。Flutter 已经归属到了大前端体系下,值得尝试。