Flutter的路由管理

275 阅读5分钟

Flutter的路由管理

路由管理(Route Control)即是管理页面之间如何跳转,也被称为导航管理。通常导航管理会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。

1. Navigator组件

Navigator是一个路由管理组件,主要通过一个路由栈来管理页面的跳转。

1.1 Navigator的构造函数

const Navigator({
    Key? key,
    List<Page> pages: const <Page<dynamic>>[],
    PopPageCallback? onPopPage,
    String? initialRoute,
    RouteListFactory onGenerateInitialRoutes: Navigator.defaultGenerateInitialRoutes,
    RouteFactory? onGenerateRoute,
    RouteFactory? onUnknownRoute,
    TransitionDelegate transitionDelegate: const DefaultTransitionDelegate<dynamic>(),
    bool reportsRouteUpdateToEngine: false,
    List<NavigatorObserver> observers: const <NavigatorObserver>[],
    String? restorationScopeId
})

1.2 Navigator的常用方法

Future push(BuildContext context, Route route)

将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的返回数据。

bool pop(BuildContext context, [ result ])

将栈顶路由出栈,result为页面关闭时返回给上一个页面的数据。

1.3 MaterialPageRoute

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

构造函数:

 MaterialPageRoute({
    WidgetBuilder builder,  //构建路由界面,返回widget
    RouteSettings settings, //路由配置信息,如路由名称
    bool maintainState = true,  //默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为false。
    bool fullscreenDialog = false,  //表示新的路由页面是否是一个全屏的模态对话框
  })

1.4 示例

主页面代码: 在mian.dart中输入如下代码

import 'package:flutter/material.dart';
import 'package:flutter_wiget_demo/RouteDemo.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage()
    );
  }
}

class HomePage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return _HomePage();
  }
}


class _HomePage extends State<HomePage>{
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        TextButton(onPressed: (){
          Navigator.push(context,
              MaterialPageRoute(builder: (context){
                return new RouteDemo();
              }));
        },
            style: ButtonStyle(
                padding: MaterialStateProperty.all(EdgeInsets.all(20)),
                backgroundColor:MaterialStateProperty.resolveWith((states) => Colors.blue)
            ),
            child: Text("open new page",
              style: TextStyle(
                color: Colors.white
              ),
            )
        )
      ],
    );
  }
}

主页面效果图

跳转页面代码: 新建RouteDemo.dart文件,并输入如下代码

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

class RouteDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("route demo"),
      ),
      body: Center(
        child: Text("This is route demo"),
      ),
    );
  }
}

跳转页面效果图

注意:

  1. Navigator的传入参数context必须为StatefulWiget的context
  2. Navigator的根布局不能为MaterialApp

2. 路由传值

在路由跳转时我们需要带一些参数,比如打开商品详情页时,我们需要带一个商品id,这样商品详情页才知道展示哪个商品信息;又比如我们在填写订单时需要选择收货地址,打开地址选择页并选择地址后,可以将用户选择的地址返回到订单页等等。

具体来说:

1.如果我们需要将参数传递给打开的新页面只需要通过在新页面的代码中加入相应的成员变量并通过构造函数传值即可。

2.如果我们需要接受新页面的返回值,则需要通过设置Navigator.pop()的第二个参数来携带返回参数。同时,在第一个页面中我们需要异步等待该返回结果。

具体代码如下:

//第一个页面代码
//第一个页面通过构造函数向第二个页面传递参数,同时异步等待第二个页面传回的结果。
class RouterTestRoute extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: TextButton(
        onPressed: () async {
          //打开TipRoute,异步等待返回结果
          var result = await Navigator.push(
            context,
            MaterialPageRoute(builder: (context){
              return TipRouteDemo(text: "我是主页面传递进来的路由参数!");
            })
          );
          print("路由返回值:${result}");
      },
        child: Text("打开另外一个页面"),
      ),
    );
  }
}
//第二个页面代码
//第二个页面通过设置Navigator.pop()中的第二个参数来达到返回消息的目的。
class TipRouteDemo extends StatelessWidget{
  final String text;
  TipRouteDemo({
    Key key,
    @required this.text
    }):super(key:key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("路由传值测试")
      ),
      body: Padding(
        padding: EdgeInsets.all(20),
        child: Center(
          child: Column(
            children: <Widget>[
              Text(text),
              TextButton(onPressed: (){
                Navigator.pop(context, "我是第二个页面的返回值"); //通过pop()传递一个返回值
              }, child: Text("返回")),
            ],
          ),
        ),
      ),
    );
  }
}

第一个页面代码效果

第二个页面代码效果

终端输出第二个页面的返回值

第二个页面点击"返回"按钮和导航bar上的返回图片所得到的返回值是不同的

3. 路由命名

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

3.1 路由表

要想使用命名路由,我们必须先提供并注册一个路由表(routing table),这样应用程序才知道哪个名字与哪个路由组件相对应。其实注册路由表就是给路由起名字,路由表的定义如下:

Map<String, WidgetBuilder> routes;

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

3.2 注册路由表

路由表的注册是在MaterialApp中添加routes属性,该属性的值即为一个路由表的Map,代码如下:

import 'package:flutter/material.dart';
import 'package:flutter_wiget_demo/RouteDemo.dart';
import 'package:flutter_wiget_demo/TipRouteDemo.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      initialRoute: "/",  //名为"/"的路由作为应用的home(首页)
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      //注册路由表
      routes: {
        "page1":(context)=>RouteDemo(),
        "page2":(context)=>RouterTestRoute(),
        "/":(context)=>HomePage() //注册路由表首页
      },
    );
  }
}


3.3 使用路由表导航

通过路由名称来打开新路由,可以使用Navigator 的pushNamed方法,Navigator 除了pushNamed方法,还有pushReplacementNamed等其他管理命名路由的方法。

Future pushNamed(BuildContext context, String routeName,{Object arguments})

因此,只需要替换Navigator.push()的代码为Navigator.pushNamed()即可:

onPressed: () {
  Navigator.pushNamed(context, "page1");
  //Navigator.push(context,
  //  MaterialPageRoute(builder: (context) {
  //  return NewRoute();
  //}));  
},

3.4 命名路由参数传递

step1: 注册一个路由

 routes:{
 "new_page":(context) => EchoRoute(),
} ,

step2: 在路由页通过RouteSetting对象获取路由参数:

class EchoRoute extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    //获取路由参数  
    var args=ModalRoute.of(context).settings.arguments;
    //...省略无关代码
  }
}

step3: 在打开路由时传递参数

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

参考文献

[1] book.flutterchina.club/