Flutter入门-路由

229 阅读7分钟

在 Flutter 中,路由技术的核心概念包括两个要素:Route 和 Navigator Route 代表应用中的一个页面,它包含了页面的布局、逻辑以及生命周期等信息。在 Flutter 中,Route通常是一个继承自PageRoute的类。

Route

PageRoute是一个抽象类,表示一个可以用于Navigator的页面。它包含了页面的构建方法、过渡动画以及页面的生命周期回调等属性。在实际开发中,我们通常会使用以下两种PageRoute:MaterialPageRouteCupertinoPageRoute

  • MaterialPageRoute:一个实现了Material Design风格的页面路由,它提供了平台特定的页面切换动画。在Android设备上,页面从底部滑入;在iOS设备上,页面从右侧滑入。如:

    // 使用MaterialPageRoute创建一个新页面
    MaterialPageRoute(builder: (context) => NewPage());
    
     MaterialPageRoute({
        WidgetBuilder builder,
        RouteSettings settings,
        bool maintainState = true,
        bool fullscreenDialog = false,
      })
    
  • builder 是一个 WidgetBuilder 类型的回调函数,它的作用是构建路由页面的具体内容,返回值是一个 widget。我们通常要实现此回调,返回新路由的实例。

  • settings 包含路由的配置信息,如路由名称、是否初始路由(首页)。

  • maintainState:默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置 maintainState 为 false。

  • fullscreenDialog 表示新的路由页面是否是一个全屏的模态对话框,在 iOS 中,如果 fullscreenDialogtrue,新页面将会从屏幕底部滑入(而不是水平方向)。

  • CupertinoPageRoute:一个实现了Cupertino风格(iOS风格)的页面路由,它提供了iOS平台特定的页面切换动画。如:

    // 使用CupertinoPageRoute创建一个新页面
    CupertinoPageRoute(builder: (context) => NewPage());
    

Navigator

Navigator 是一个管理应用页面栈的组件,它负责处理页面之间的跳转、导航以及参数传递等操作。它通过一个栈结构来管理应用中的页面。当一个新页面被打开时,它会被压入栈顶;当一个页面被关闭时,它会从栈顶弹出。通过对栈的操作,Navigator实现了页面间的跳转和导航。

Navigator 类是一个关键的组件,它提供了一系列方法来实现页面间的导航。通过路由直接跳转:

  • push 栈顶入栈一个新的路由
  • pushReplacement 将当前路由替换为一个新的路由
  • pushAndRemoveUntil 根据参数决定栈内路由清除多少,并入栈一个新路由。

通过命名路由跳转:

  • pushNamed 栈顶入栈一个新的路由
  • pushReplacementNamed 将当前路由替换为一个新的路由
  • pushNamedAndRemoveUntil 根据参数决定栈内路由清除多少,并入栈一个新路由

路由主动出栈:

  • pop 当前路由出栈
  • popAndPushNamed 当前路由出栈,入栈一个新路由
  • popUntil 从栈顶开始,逐个pop,直到参数中指定的路由为止
  • canPop 判断当前路由是否可以出栈,如果栈内只有当前路由,返回false,如果当前路由前面还有路由,返回true。也就是说栈底的路由,该方法返回false
  • maybePop 当前路由如能能出栈就出栈,如果不能就什么都不做。

路由删除:

  • removeRoute
  • removeRouteBelow

路由替换:

  • replace
  • replaceRouteBelow

路由判断方法:

  • of 获取当前context 的Navigator的实例.
  • 几乎所有的Navigtor方法实现都是调用of后,再调用具体方法的,如pop:
static bool pop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).pop<T>(result);
  }

静态路由

在编译时定义所有路由,并在需要导航时直接使用路由名称进行跳转

基本使用

在项目的 lib 目录下新建 routes 目录,新建 routes.dart 文件。

1. 首选需要引入,不然会报错。

import "package:flutter/material.dart";

2. 引入页面文件

arduino
 代码解读
复制代码
import 'package:fushikang_flutter/pages/login_page.dart';
import 'package:fushikang_flutter/pages/index_page.dart';

3. 配置命名路由

ini
 代码解读
复制代码
final routes = {
  '/index': (context) => IndexPage(),
};

//固定写法
var onGenerateRoute = (RouteSettings settings) {
  // 统一处理
  final String name = settings.name;
  final Function pageContentBuilder = routes[name];
  final Route route = MaterialPageRoute(builder: (context) => pageContentBuilder(context));
  return route;
};

'/index'是路由名称,这样我们的 routes.dart 文件就配置好了。

配置 main.dart

1. 我们回到 mian.dart 首先需要把routes文件引入到main中。

import 'routes/routes.dart';

2. 配置路由

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'test',
      initialRoute: '/index', // 这是配置加载的第一个页面
      onGenerateRoute: onGenerateRoute, // 重点看这里
    );
  }
}

3. 跳转页面

配置完成之后重新编译项目,在项目中调用以下代码就可以实现跳转页面了。

Navigator.pushNamed(context, '/index');

静态路由传参

1. 修改routes文件了。

final routes = {
  // 传参
  '/login': (context, {arguments}) => LoginPage(arguments: arguments),
  // 不传参
  '/index': (context) => IndexPage(),
};

//固定写法-路由传参
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;
    }
  }
};

2. 页面接受参数

某个页面需要传参,就需要在页面中这样写才能接收到参数,否则arguments就会是null

class LoginPage extends StatefulWidget {
  final Map arguments;
  LoginPage({Key key, this.arguments}) : super(key: key);
  @override
  _LoginPageState createState() => _LoginPageState(arguments: this.arguments);
}

class _LoginPageState extends State<LoginPage> {
  Map arguments;
  _LoginPageState({this.arguments});
  ...
}

3. 跳转页面并传参

以上都配置完成之后,重新编译项目在项目中调用以下代码就能实现路由传参。

Navigator.pushNamed(context, '/login', arguments: {"checkCode": 123456});

接受跳转返回的参数

A页面代码: 使用Navigator push一个新页面,pushNamed方法是有一个Future的返回值的,在then回调中监听并接收新页面回传回来的数据,并且借助showDialog显示在Dialog

import 'package:flutter/material.dart';
import 'package:flutter_app/pages/simpleWidget/navigator/StaticNavigatorPageWithParams.dart';

void main() {
  runApp(
      new MaterialApp(home: new FlutterDemo(), routes: <String, WidgetBuilder>{
    'router/new_page_with_callback': (_) => new StaticNavigatorPageWithResult()
  }));
}

class FlutterDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Flutter进阶之旅"),
      ),
      body: new Center(
          child: new RaisedButton(
              child: new Text("静态路由接收下一页返回值"),
              onPressed: () {
                Navigator.of(context)
                    .pushNamed('router/new_page_with_callback')
                    .then((value) {
                  showDialog(
                      context: context,
                      child: new AlertDialog(
                        content: new Text(value),  
                      ));
                });
              })),
    );
  }
}

B页面代码:

从效果图中可以看到,当我点击B页面中间的按钮时会把数据回传给上一页,但是直接点击导航栏左上角的返回按钮回到A页面时并不会把数据传递给上一个页面,这里是因为我在B页面的按钮上pop页面出栈的时候把参数放进里面作为了参数传递,pop()可接收一个Object对象作为参数传递

Navigator.of(context).pop(T extends Object);

这就告诉我们当我们需要给上一个页面回传数据的时候可直接借助pop传递Navigator.of(context).pop("页面结束后返回的数据");,不需要传值的时候直接返回空对象Navigator.of(context).pop()即可

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

配置路由守卫

前端开发过程中,页面跳转时会有路由守卫。而我们的flutter也有同样的功能。

1. 修改 routes 文件

在文件的最下方加入以下代码

//导航的观察者
//导航路由在跳转时的回调,比如 push,pop,remove,replace是,可以拿到当前路由和后面路由的信息
//继承NavigatorObserver
class NewObserver extends NavigatorObserver {
  @override
  void didPush(Route route, Route previousRoute) {
    // 当调用Navigator.push时回调
    super.didPush(route, previousRoute);
    //可通过route.settings获取路由相关内容
    print('跳转到下一个页面');
  }

  @override
  void didPop(Route route, Route previousRoute) {
    
    // 当调用Navigator.pop时回调
    super.didPop(route, previousRoute);
    print('跳回到上一个页面');
  }

  @override
  void didRemove(Route route, Route previousRoute) {
    
    // 当调用Navigator.Remove时回调
    print('调用Navigator.Remove');
    super.didRemove(route, previousRoute);
    
  }
}

2. 配置路由守卫

回到 main.dart 主入口文件

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'test',
      initialRoute: '/login',
      onGenerateRoute: onGenerateRoute,
      //导航的观察者
      navigatorObservers: <NavigatorObserver>[NewObserver()],
    );
  }
}

动态路由

在Flutter中,动态路由是根据条件或用户操作来决定要导航到哪个页面,并可以通过传递参数来自定义每个页面。您可以使用Navigator.push方法和MaterialPageRoute来实现动态路由

  • Navigator.push(context, MaterialPageRoute(builder: (context) => Screen()))导航到指定的页面组件。
  • MaterialPageRoute(builder: (context) => Screen())创建一个新的页面路由。
  • builder定义页面组件的构建函数。
  • arguments传递给目标页面的参数。

跳转传值

//Home.dart
//只贴按钮的代码,其余的和上面一样

RaisedButton(
	child: Text('跳到详情页面'),
	onPressed: (){
	  //跳转页面
	  Navigator.of(context).push(
		MaterialPageRoute(
		  //传值
		  builder: (context)=>Detail(Test:'我是参数')
		  //没传值
		  //builder: (context)=>Detail()
		)
	  );
	},
  )

然后在目标页面接收,如果没传值默认为上面定义的默认值

Detail.dart文件代码:

class Detail extends StatelessWidget {
  //需要定义变量和默认值
  String Test;
  Detail({this.Test='没有给我传值'});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //浮动按钮
      floatingActionButton: FloatingActionButton(
        child: Text('返回'),
        onPressed: (){
          Navigator.of(context).pop();
        },
      ),
      appBar: AppBar(title: Text("详情页面"),),
      body: Text(this.Test),
    );
  }
}