Flutter系列笔记-7.Flutter路由管理

没有注释的代码不是好代码

没有demo的博客不是好博客

本博客代码请移步github

什么是路由管理

Flutter里的路由管理( NavigatorRoutes)对应安卓里的Intent

Android 中,Intent 主要有两个使用场景:在 Activity 之前进行导航,以及组件间通信

Flutter 实际上并没有 ActivityFragment 的对应概念。在 Flutter 中你需要使用 NavigatorRoute 在同一个 Activity 内的不同界面间进行跳转。

Route 是应用内屏幕和页面的抽象,Navigator 是管理路径 route 的工具。

一个 route 对象大致对应于一个 Activity,但是它的含义是不一样的。Navigator 可以通过对 route 进行压栈和弹栈操作实现页面的跳转。

Navigator 的工作原理和栈相似,你可以将想要跳转到的 route 压栈 (push()),想要返回的时候将 route 弹栈 (pop())。
可以查看相应的翻译了解 更多

关于IOS里相对应的概念可以查看这里

Navigator相关路由管理方法

of 主要是获取NavigatorState对象对路由进行push pop replace remove等操作

普通路由管理方法

push 将设置的router信息推送到Navigator上,实现页面跳转。

pop 关闭当前页面

popUntil 反复执行pop 直到该函数的参数predicate返回true为止。

pushAndRemoveUntil 将给定路由推送到Navigator,一个一个地删除先前的路由,直到该函数的参数predicate返回true为才停止。

pushReplacement 用新的路由替换当路由。

命名路由管理方法

pushNamed 效果等同于push

pushNamedAndRemoveUntil 效果等同pushAndRemoveUntil

pushReplacementNamed 效果同pushReplacement

popAndPushNamed 关闭当前页面,并导航到新页面

其他方法

canPop 判断是否可以导航到新页面

maybePop 可能会导航到新页面

removeRoute 从Navigator中删除指定的路由,同时执行Route.dispose操作。

removeRouteBelow 从Navigator中删除指定路由的前一个路由,同时执行Route.dispose操作

replace 将Navigator中指定的路由替换成一个新路由。

replaceRouteBelow 将Navigator中的指定路由的前一个路由,替换成相应的路由

1.Navigator.push 跳转到新页面

先看Navigator.push方法的定义
接收两个参数,并且有返回值Future<T>,可以异步获取到返回值

@optionalTypeArgs
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
  return Navigator.of(context).push(route);
}

Route可以直接使用的子类有

  1. MaterialPageRoute 安卓风格 从下往上淡入 退出时相反
    Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
      return PublishBlogPage();
    }));
  1. CupertinoPageRoute ios风格 进入时动画从右侧往左滑入,退出时相反

     Navigator.push(context, CupertinoPageRoute(builder: (BuildContext context) {
       return PublishBlogPage();
     }));
    
  2. PageRouteBuilder 这个有一个必须外部传入的pageBuilder参数,一般用于自定义页面跳转动画

     Navigator.push(context, PageRouteBuilder(pageBuilder: (BuildContext context,
         Animation<double> animation, Animation<double> secondaryAnimation) {
       return new FadeTransition(
         //使用渐隐渐入过渡,
         opacity: animation,
         child: PublishBlogPage(),
       );
     }));
    

获取第二个页面的返回结果

可以使用then

Navigator.push(context, CupertinoPageRoute(builder: (BuildContext context) {
  return PublishBlogPage();
})).then((result){
  print('result:$result');
});

也可以使用await ,推荐使用 async/await

void _publishBlog(BuildContext context) async {
  //MaterialPageRoute 安卓风格 从下往上淡入 退出时相反
  var reslut = await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
    return PublishBlogPage();
  }));
  print('result:$reslut');
}

  如果使用Navigator.push方法跳转到的第二个界面使用了Scaffold作为根Widget并设置了appBar,appBar会默认有一个返回按钮,点击返回按钮或者模拟器的返回键,可以关闭页面,返回时返回给上一个页面的结果是null。,如果自定义的页面不使用Scaffold,没有返回按钮怎么办,或者需要返回自定义的结果给上一个页面时怎么办,可以使用 Navigator.pop(context) 或者Navigator.of(context).pop()

    
screenshot-2019-12-24_21.54.55.491.png

2.Navigator.pop 关闭当前页面

关闭当前页面可以使用Navigator.pop(context)或者Navigator.of(context).pop()

Navigator.pop方法的定义

@optionalTypeArgs
static bool pop<T extends Object>(BuildContext context, [ T result ]) {
  return Navigator.of(context).pop<T>(result);
}

Navigator.of(context).pop()方法的定义

@optionalTypeArgs
bool pop<T extends Object>([ T result ]) {....}

从方法定义上看,两个是等价的,因为Navigator.pop使用的是Navigator.of(context).pop()

1.不需要返回结果给上一个页面时

Navigator.pop(context);

2.需要返回结果给上一个页面时

//可以返回任意数据类型
//Navigator.pop(context,"this is result");
//Navigator.pop(context,DateTime.now());
//返回自定义对象
Navigator.pop(context,BlogModel(title:"无题",content: "testtesttest",author: "无名",publishedTime: DateTime.now()));

3.Navigator.push携带参数给第二个页面

Navigator.pop有一个可选参数用于返回结果给上一个页面

但是Navigator.push没有可选参数,使用时,也只是创建了一个Widget返回

Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
  return PublishBlogPage();
}));

那么怎么传参数给第二个页面?因为Navigator.push调用时,需要创建初始化第二个页面对象,一般来说都是通过构造函数传参,也就是创建第二个页面的对象时,直接赋值,第二个页面就可以直接使用设置的值,以达到跳转时携带参数的目的

Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
  return PublishBlogPage(blogModel: BlogModel(title:"默认标题",content: "这是默认内容"),);
}));

4.其他Navigator和Route配合使用的方法

pushReplacement

使用新的Route替换当前Route,当前Route会被销毁,并且可以返回一个result给前一个页面,
result是可选的,不赋值默认返回null给前一个页面

@optionalTypeArgs
static Future<T> pushReplacement<T extends Object, TO extends Object>(BuildContext context, Route<T> newRoute, { TO result })

pushReplacement使用示例

从页面A,跳转到页面B,页面B调用了pushReplacement把页面B替换成了页面C,并返回值"pushReplacement resule"给页面A

Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context){
  return ThirdTestPage();
}),result:"pushReplacement resule");

pushAndRemoveUntil

向栈里添回一个newRoute,并且一个一个地移除之前栈里的Route,直到RoutePredicate方法返回true才停,一般RoutePredicate可以配置route.settings来配合使用

@optionalTypeArgs栈
static Future<T> pushAndRemoveUntil<T extends Object>(BuildContext context, Route<T> newRoute, RoutePredicate predicate)

pushAndRemoveUntil使用示例

   Navigator.of(context).pushAndRemoveUntil(
              MaterialPageRoute(builder: (BuildContext context) {
            return CommonPage();
          }), (Route route) {
            //一直关闭,直到首页时停止,停止时,整个应用只有首页和当前页面
            if (route.settings?.name == "/") {
              return true; //停止关闭
            }
            return false; //继续关闭
            //return route==null; //一直关闭页面,直到全部Route都关闭,效果就是整个应用,只剩下当前页面,按返回键会直接回系统桌面
          });

popUntil

一个一个地移除之前栈里的Route,直到RoutePredicate方法返回true才停止

 static void popUntil(BuildContext context, RoutePredicate predicate) {
    Navigator.of(context).popUntil(predicate);
 }

popUntil使用示例

Navigator.popUntil(context,(Route route){
            print('route$route');
            //到首页时停下,不要销毁首页,
            //如果想整个应用只剩当前页面,可以省略下面逻辑 return route==null; 即可
            if(route?.settings?.name == "/") {
              return true;
            }
            return false;
          });

5.命名路由

  命名路由的push方法和普通路由的使用方法大同小异,只是普通路由要创建一个Route来实现页面跳转,命名路由使用一个字符来实现页面跳转,跳转时flutter会在路由表里根据字符串找到要跳转到哪一个页面

命名路由一般都是配合MaterialApp或者CupertinoApp使用

@override
Widget build(BuildContext context) {
return MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  home: MyHomePage(title: "路由管理"),

  //路由映射
  routes: myRoutes,

  //指定哪个命名路由指向的页面作为首面,这个值生效时上面的home不生效
  initialRoute: "/",

  //在使用命名路由跳转时,如果路由名称没有注册,找不到要跳转到哪里,此方法生效
  onGenerateRoute: (RouteSettings settings) {
    print('onGenerateRoute:$settings');
    if(settings.name== unknowRouteName) {
      return null;
    }
    return MaterialPageRoute(builder: (BuildContext context) {
      return NoFoundPage();
    });
  },

  //在使用命名路由跳转时,如果路由名称没有注册,找不到要跳转到哪里,
  // 并且没有实现onGenerateRoute方法,或者onGenerateRoute方法返回null,此方法生效
  //如果这一个方法也是返回null,则控制台会输出错误信息,这一个方法的返回值不应该为null
  onUnknownRoute: (RouteSettings settings){
    print('onUnknownRoute:$settings');
    return MaterialPageRoute(builder: (BuildContext context) {
      return NoFoundPage();
    });
  },
 );
}

命名路由使用规律

当不使用命名路由时,应用使用哪个用页面作为应用首页用home属性配置

当使用routes了注册了路由表时,首页可以使用initialRoute属性配置,"/"是默认的首页,想使用别的页面作为首页时,给initialRoute赋值路由表里相应的路由名称就可以了,当initialRoute生效时home属性不生效

当使用命名路由跳转时,如果路由名称在路由表里找不到,则会先调用onGenerateRoute配置的方法生成一个路由,如果onGenerateRoute配置的方法返回了有效的路由(非null),则显示相应页面,如果onGenerateRoute配置的方法返回的是null,则调用onUnknownRoute配置的方法生成路由显示页面,onUnknownRoute不应该返回null,否则会在控制台输出错误日志

其他方法路由管理方法如何时使用

removeRoute

从Navigator中删除路由,同时执行Route.dispose操作。被删除的路由一定是存在的历史路由,不然抛异常 The given route must be in the history; this method will throw an exception if it is not.

static void removeRoute(BuildContext context, Route<dynamic> route) {
  return Navigator.of(context).removeRoute(route);
}

removeRouteBelow

删除指定路由的前一个路由,指定的路由要存在路由线路中,并且指定路由有前一个存在的路由,否则抛异常

举例:路由跳转线路好下 首页->A页面->B页面->C页面 在C页面里有一个按钮,点击时调用removeRouteBelow并指定anchorRoute为跳转到C页面时生成的路由,当按钮点击第一次时B页面被删除,当按钮点击第二次时A页面被删除,当按钮点击第三次时首页被删除,再点击就抛异常了

static void removeRouteBelow(BuildContext context, Route<dynamic> anchorRoute) {
  return Navigator.of(context).removeRouteBelow(anchorRoute);
}   

replace

将指定的路由替换成一个新路由。被替换的路由不能是当前可见的,The old route must not be currently visible

@optionalTypeArgs
static void replace<T extends Object>(BuildContext context, { @required Route<dynamic> oldRoute, @required Route<T> newRoute }) {
  return Navigator.of(context).replace<T>(oldRoute: oldRoute, newRoute: newRoute);
}   

replaceRouteBelow

将指定的路由的前一个路由替换成新的路由

@optionalTypeArgs
static void replaceRouteBelow<T extends Object>(BuildContext context, { @required Route<dynamic> anchorRoute, Route<T> newRoute }) {
  return Navigator.of(context).replaceRouteBelow<T>(anchorRoute: anchorRoute, newRoute: newRoute);
}

以上几个方法的使用,都需要知道路由线路里已经存在的路由才可以调用,Navigator.of(context)得到的NavigatorState对象里有两个成员变量

final List<Route<dynamic>> _history = <Route<dynamic>>[];

final Set<Route<dynamic>> _poppedRoutes = <Route<dynamic>>{};

但是都是私有的,外部无法访问,FlutterSDK也没有提供对外访问的方法。
但是Flutter提供了一个NavigatorObserver导航器观察者,配合MaterialApp或者CupertinoAppnavigatorObservers属性可以实现保存路由线路。自己实现NavigatorObserver保存路由线路,并进行状态管理就可以调用上面的方法了,具体代码请看demo,如果看完这篇博客,还是不太懂,可以运行demo加深印象,加强理解。demo地址在博客开头

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335