一个App是由很多page(页面)组成的,咱们需求点击跳转到不同的页面,而不是仅仅单纯经过BottomNavigationBarItem点击设置IndexedStack的特点进行切换页面。通常咱们会经过路由来统一的办理跳转。

什么是路由

存在一个路由映射表,经过仅有的标识找到要跳转的页面。在Flutter中,路由办理主要有两个类:RouteNavigator

Route

一个页面要想被路由统一办理,有必要包装为一个 Route。MaterialPageRoutePageRoute的子类,表明一个模态路由页面,还界说了路由构建及切换时过渡动画的相关接口及特点。MaterialPageRoute 是 Material 组件库供给的组件,它能够针对不同渠道,实现与渠道页面切换动画风格一致的路由切换动画。

  • 在 iOS 渠道,翻开一个页面会从右边滑动到屏幕左面,回来时会从左面到右边消失
  • Android 渠道,翻开一个页面会从屏幕底部滑动到屏幕的顶部,封闭页面时从顶部滑动到底部消失

承继联系是这样的

MaterialPageRoute->PageRoute->ModalRoute->TransitionRoute->OverlayRoute->Route

Navigator

Navigator:办理一切的 Route 的 Widget,经过栈来进行办理的,通常当时屏幕显现的页面便是栈顶的路由。

组件路由

路由跳转,传入一个路由对象route。该方法是把新的路由添加到Navigator办理的路由对象的栈顶

Navigator.of(context).push(route)

回来上个页面,这儿回来时能够带参数的[ T? result ],退出则是从栈顶把路由对象移出

Navigator.of(context).pop()

示例代码

child: ElevatedButton(
  onPressed: (){
    Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){
      return PageDetail();
    }));
  },
  child: Text('进入概况页面'),
),
child: ElevatedButton(
  onPressed: (){
    Navigator.of(context).pop();
  },
  child: Text('回来'),
),

导航回来拦截

为了防止用户误触回来按钮而导致 App 退出,在很多 App 中都拦截了用户点击回来键的按钮,然后进行一些防误触判别,比方当用户在某一个时间段内点击两次时,才会认为用户是要退出(而非误触)。Flutter 中能够经过WillPopScope来实现回来按钮拦截,咱们看看 WillPopScope 的默认构造函数:

const WillPopScope({
  ...
  required WillPopCallback onWillPop,
  required Widget child
})

onWillPop 是一个回调函数,当用户点击回来按钮时被调用(包含导航回来按钮及 Android 物理回来按钮)。该回调需求回来一个Future对象,假如回来的 Future 最终值为false时,则当时路由不出栈(不会回来);最终值为true时,当时路由出栈退出。咱们需求供给这个回调来决定是否退出。

@override
Widget build(BuildContext context) {
  WillPopScope(
      onWillPop: onWillPop,
      child: Scaffold(
      )
  )
  onWillPop:() {
    print('即将退出本界面');
    //return Future.value(false);//阻挠退出
    return Future.value(true); //退出
  }
}

命名路由

基本路由运用仍是比较简单的,可是对于页面比较多的场景就不太习惯用了,每次跳转新页面就要创立新路由,这样便是比较紊乱了。而命名路由是给每个页面起了一个别号,经过这个别号就能够找到翻开这个页面,这样办理起来就比较明晰便利了。

要想经过别号来实现页面切换,就需求用到 MaterialApp 供给的一个页面名称映射规则,也便是路由表。这个路由表是一个Map结构,key是页面的别号,value便是对应页面

Navigator 运用的4个关键特点

  • initialRoute: 初始路由的,也便是进入APP,默认页面
  • onGenerateRoute: 路由拦截器,当需求对某个路由做特别处理时能够运用这个
  • onUnknownRoute: 找不到页面,默认创立一个错误页面
  • routes:也就在履行路由跳转的时分,会到路由调集里边的子路由进行匹配,假如匹配 到那么就调整到指定页面

示例代码

封装一个 routes.dart

import 'package:flutter/material.dart';
import '../pages/tabs.dart';
import '../pages/search.dart';
import '../pages/product.dart';
//装备路由,界说Map类型的routes,Key为String类型,Value为Function类型
final Map<String,Function> routes={
      '/':(context)=>Tabs(),
      '/product':(context)=>ProductPage(),
      '/search':(context,{arguments})=>SearchPage(arguments:arguments),
};
//固定写法
var onGenerateRoute=(RouteSettings settings) {      
      //String? 表明name为可空类型
      final String? name = settings.name; 
      //Function? 表明pageContentBuilder为可空类型
      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;
        }
      }
};

在main.dart里边装备

void main() => runApp(MyApp());
class MyApp extends StatelessWidget {  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // home:Tabs(),   
      initialRoute: '/',     //初始化的时分加载的路由
      onGenerateRoute: onGenerateRoute
    );
  }
}

跳转代码

ElevatedButton(
    child: Text("跳转到产品页面"),
    onPressed: () {
      Navigator.pushNamed(context, '/product');
    }
),  

路由钩子

MaterialApp有一个onGenerateRoute特点,当调用Navigator.pushNamed(…)翻开命名路由时,假如指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;假如路由表中没有注册,才会调用onGenerateRoute来生成路由。然后能够在路由中经过参数RouteSettings来判别是否需求特别处理,比方能够把需求权限判别跳转的路由放到这儿翻开。

onGenerateRoute: (settings) {
  if (settings.name == "login") {
    return MaterialPageRoute(
        builder: (context) {
          return LoginPage(settings.arguments);
        }
    );
  }
  return null;
},

Navigator 怎么传参和回传值

  1. 注册好之后就能够经过 Navigator.pushNamed() 翻开页面了
Navigator.pushNamed(context, "basic");
//带参数的跳转
Navigator.pushNamed(context, "basic", arguments: '要传的参数');
  1. 获取传递的参数
Widget build(BuildContext context) {
  // 1.获取数据
  final args= ModalRoute.of(context)!.settings.arguments;
}
  1. 回来页面是经过 Navigator.pop
Navigator.pop(context);
Navigator.pop(context, "回来值");
  1. 接纳回来的数据,需求修正下跳转方法
Navigator.pushNamed(context, 'basic').then((value) => print('接纳到的数据${value}'));
  1. 回来到指定的页面是经过 Navigator.popUntil
Navigator.popUntil(context, ModalRoute.withName('路由'));

在平常运用过程中,是能够把RoutesonGenerateRoute单独提取出来成为一个类,这样后期保护就比较明晰和修正便利。

命名路由的最重要作用,便是建立了字符串标识符与各个页面之间的映射联系,使得各个页面之间彻底解耦,运用内页面的切换只需求经过一个字符串标识符就能够搞定,为后期模块化打好基础。

Navigator的各种跳转方法详解

  • pushAndRemoveUntil:跳转到新的页面,并且删去新页面和底部页面之间一切页面
  • pushReplacement:新的页面替换当时页面,只需求创立新的页面,当时页面毁掉
  • pushReplacementNamed:新的页面替换当时页面,仅仅路由的传递,命名路由方法,当时页面毁掉
  • popUntil:回来到指定页面,其他页面毁掉
  • popAndPushNamed:退出当时页面并从弹出新的页面
  • canPop:判别当时页面能否被弹出栈,栈内只要一个页面时为false,别的时分为true
  • maybePop:判别根据便是看当时路由是否处在栈中“最底部”的位置,假如不是就退出

pushReplacementNamed和popAndPushNamed

有 A、B、C 三个页面,A页面经过 pushNamed 跳转到 B,B 经过 pushReplacementNamed 跳转到 C,点击 C 页面按钮履行 pop。点击 C 页面按钮直接回来到了 A 页面,而不是 B 页面,由于 B 页面运用 pushReplacementNamed 跳转,路由仓库改变:

Flutter-导航与路由堆栈详解

假如 B 页面跳转到 C 页面,运用 popAndPushNamed,popAndPushNamed 路由仓库和 pushReplacementNamed 是一样,仅有的区别便是 popAndPushNamed 有 B 页面退出动画。

pushNamedAndRemoveUntil

有如下场景,运用程序进入主页,点击登录进入登录页面,然后进入注册页面或许忘掉密码页面…,登录成功后进入其他页面,此刻不期望回来到登录相关页面,此场景能够运用 pushNamedAndRemoveUntil。

有A、B、C、D 四个页面,A 经过push进入 B 页面,B 经过push进入 C 页面,C 经过 pushNamedAndRemoveUntil 进入 D 页面同时删去路由仓库中直到 /B 的路由,C 页面代码:

RaisedButton(
  child: Text('C 页面'),
  onPressed: () {
    Navigator.of(context).pushNamedAndRemoveUntil('/D', ModalRoute.withName('/B'));
  },
),

D 页面按钮履行 pop,从 C 到 D 仓库的改变

Flutter-导航与路由堆栈详解

Navigator.of(context).pushNamedAndRemoveUntil('/D', ModalRoute.withName('/B'));

表明跳转到 D 页面,同时删去D 到 B 直接一切的路由,假如删去一切路由,只保存 D

Navigator.of(context).pushNamedAndRemoveUntil('/D', (Route route)=>false);

路由仓库改变:

Flutter-导航与路由堆栈详解

popUntil

有A、B、C、D 四个页面,D 页面经过 popUntil 一向回来到 A 页面,D 页面代码:

RaisedButton(
  child: Text('D 页面'),
  onPressed: () {
    Navigator.of(context).popUntil(ModalRoute.withName('/A'));
  },
)

maybePop 和 canPop

在 A 页面时路由仓库中只要 A,调用 pop 后,路由仓库改变

Flutter-导航与路由堆栈详解

此刻路由仓库为空,没有可显现的页面,运用程序将会退出或许黑屏,好的用户体验不应如此,此刻能够运用 maybePop,maybePop 只在路由仓库有可弹出路由时才会弹出路由

上面的事例在 A 页面履行maybePop:

RaisedButton(
  child: Text('A 页面'),
  onPressed: () {
    Navigator.of(context).maybePop();
  },
)

点击后不会出现弹出路由,由于当时路由仓库中只要 A,在 B页面履行maybePop,将会回来到 A 页面。

也能够经过 canPop 判别当时是否能够 pop:

RaisedButton(
  child: Text('B 页面'),
  onPressed: () {
    if(Navigator.of(context).canPop()){
      Navigator.of(context).pop();
    }
  },
)

三方插件 fluro

fluro 作为一款优秀的 Flutter 企业级路由框架,fluro的运用比官方供给的路由框架要复杂一些,可是却非常合适中大型项目。由于它具有层次分明、条理化、便利扩展和便于全体办理路由等优点。 fluro 的github链接:github.com/lukepighett…

参阅文章:这篇文章对于路由仓库解说的很详细:/post/687286…

监听路由仓库的改变:/post/687322…