问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501
你好,欢迎来到懂视!登录注册
当前位置: 首页 - 正文

全网最简单的FlutterNavigator2.0路由指南

发布网友 发布时间:2024-09-26 15:27

我来回答

1个回答

热心网友 时间:2024-10-04 11:04

准备

关于Flutter路由的一些原理,可以阅读我们之前的文章《Flutter路由源码剖析》,本文我们主要来学习一下Navigator2.0的用法。

为了演示Navigator2.0的用法,这里准备了一个简单案例,项目下载访问这里。

nav_demo目录是一个使用Navigator1.0的示例,总共4个页面,分别是:splash、login、home、details

代码结构如下:

核心代码:

classMyAppextendsStatelessWidget{constMyApp({Key?key}):super(key:key);@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Navigator1.0',routes:<String,WidgetBuilder>{'/login':(_)=>constLogin(),'/home':(_)=>constHome(),},debugShowCheckedModeBanner:false,theme:ThemeData(primarySwatch:Colors.blue,),home:constSplash(),);}}

Navigator1.0存在的几个问题:

静态路由不方便给构造函数传参,不如动态路由灵活。如不考虑使用第三方路由框架时,常常会存在静态路由和动态路由一起使用的情况。如下代码,点击网格项,拉起详情页

GestureDetector(onTap:(){Navigator.push(context,MaterialPageRoute(builder:(ctx)=>Details(_movieList![i].name,_movieList![i].imgUrl)));},child:Column(mainAxisSize:MainAxisSize.min,children:[Flexible(child:Image.network(_movieList![i].imgUrl)),Text(_movieList![i].name),],),);

难以处理Web版上,地址栏URL做路由导航的需求。如下图,切换路由,地址栏没有变化

对路由的控制极不灵活,譬如对路由嵌套的场景需求;处理Android返回键需求等

编程风格不统一。Navigator1.0是一种命令式编程范式,而Flutter本身是一种声明式的编程范式。Navigator2.0回归声明式的范式,更具Flutter的味道。

使用Navigator2.0重构

这里,我先以一种最少的修改,最简单的使用方式来重构,看看Navigator2.0是如果使用的

修改代码结构:

仅添加router文件夹,新增delegate.dart文件。在其中自定义类MyRouterDelegate继承自RouterDelegate,并混入ChangeNotifier和PopNavigatorRouterDelegateMixin。

这里有三个方法必须实现:

classMyRouterDelegateextendsRouterDelegate<List<RouteSettings>>withChangeNotifier,PopNavigatorRouterDelegateMixin<List<RouteSettings>>{finalList<Page>_pages=[];@overridefinalGlobalKey<NavigatorState>navigatorKey=GlobalKey<NavigatorState>();@overrideWidgetbuild(BuildContextcontext){returnNavigator(key:navigatorKey,pages:List.of(_pages),onPopPage:_onPopPage,);}@overrideFuture<void>setNewRoutePath(List<RouteSettings>configuration)async{}///………………省略部分代码………………}

setNewRoutePath方法可以先留空,主要看一下build方法。我们创建了Navigator作为路由的管理者,并设置了两个主要参数pages和onPopPage,其中pages是一个存放Page对象的列表;当路由被pop时,onPopPage会被回调,开发者可在此处理路由退栈的逻辑。

这里,我们真正需要关心的是,Page对象列表是什么东西?

我们知道,在Flutter中,使用路由这个词来表示App中的页面,路由栈也就是页面栈。2.0提出的这个Page类,实际上就相当于是一个路由的描述文件。这个思想就类似于我在《Flutter框架实现原理》一文提到的Flutter的四颗树。Flutter中的所谓Widget就是一种配置描述,而Element类就根据这个描述生成的。同理,Page也是一种描述,用于生成真正的路由对象。

理解了这一点,你就会明白,Navigator2.0不仅没有让路由管理更复杂,反而更简单了。我们只要操作这个Page列表,相应的路由栈就会感知到,自动发生变化。我们想要哪个页面显示,只需要把它放置到List的最后一个元素位置即可。Navigator2.0就是把原来对形同黑盒子的路由栈操作变成了一个对列表List的操作。我们想要改变路由栈中页面的先后顺序,只需要修改List<Page>中的元素位置。

接下来就需要看一下,如何使用Page类创建对象。Page类本身继承自RouteSettings类,这说明它确实就是一个路由配置文件。它本身是一个抽象类,不能实例化,我们找到了它的两个直接实现类:

这里我们一看就明白了,Flutter已经给我们提供好了实现类,一个是Android的Material风格,一个是iOS的Cupertino风格。

直接使用MaterialPage包装我们写的页面,增加一个方法封装这些逻辑,帮助创建Page:

MaterialPage_createPage(RouteSettingsrouteSettings){Widgetchild;switch(routeSettings.name){case'/home':child=constHome();break;case'/splash':child=constSplash();break;case'/login':child=constLogin();break;case'/details':child=Details(routeSettings.arguments!asMap<String,String>);break;default:child=constScaffold();}returnMaterialPage(child:child,key:Key(routeSettings.name!)asLocalKey,name:routeSettings.name,arguments:routeSettings.arguments,);}

此处的处理,有些类似于静态路由配置表,但是注意到,我们可以通过RouteSettings参数,给页面的构造方法传参了,比1.0的静态路由灵活许多。

好了,到这里就只需要写几个方法来操作Page列表:

///压入新页面显示voidpush({requiredStringname,dynamicarguments}){_pages.add(_createPage(RouteSettings(name:name,arguments:arguments)));//通知路由栈,我们的Page列表已经修改了notifyListeners();}///替换当前正在显示的页面voidreplace({requiredStringname,dynamicarguments}){if(_pages.isNotEmpty){_pages.removeLast();}push(name:name,arguments:arguments);}

最后,为了能使用Navigator2.0的接口,还要对main.dart中进行修改。这里,我们为了演示简单,在app.dart中实例化了一个MyRouterDelegate的全局变量:

import'package:nav2_demo/router/delegate.dart';MyRouterDelegatedelegate=MyRouterDelegate();

修改main.dart,直接引用了这个全局变量:

classMyAppextendsStatelessWidget{MyApp({Key?key}):super(key:key){//初始化时添加第一个页面delegate.push(name:'/splash');}@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Navigator2.0',debugShowCheckedModeBanner:false,theme:ThemeData(primarySwatch:Colors.blue,),home:Router(routerDelegate:delegate,backButtonDispatcher:RootBackButtonDispatcher(),),);}}

主要就是声明式的创建了一个Router,并设置了routerDelegate属性。这里backButtonDispatcher不是必须的,但我们这个案例为了演示对返回键的处理,创建了一个默认实现RootBackButtonDispatcher()。

好了,基本完成。把以前使用Navigator1.0操作路由栈的地方,全部替换成我们自己提供的接口。

//显示home页delegate.replace(name:'/home');

包括之前利用动态路由给详情页的构造方法传参的地方:

///home.dartGestureDetector(onTap:(){delegate.push(name:'/details',arguments:{'name':_movieList![i].name,'imgUrl':_movieList![i].imgUrl});},child:Column(mainAxisSize:MainAxisSize.min,children:[Flexible(child:Image.network(_movieList![i].imgUrl)),Text(_movieList![i].name),],),);

详情页接收数据:

classDetailsextendsStatelessWidget{finalStringname;finalStringimgSrc;Details(Map<String,String>arguments):name=arguments['name']!,imgSrc=arguments['imgUrl']!;///省略部分代码}

看出来了吗?我们以类似1.0的静态路由的操作方式,可以轻松的给页面构造传参了。

这就是Navigator2.0的基本使用,大家还觉得难以理解吗?

以下是完整代码,我们还重写了popRoute方法,用于处理页面退出的逻辑。很多时候,我们不希望用户点返回键时,直接退出应用,以下做了处理,当已经退到根路由页面了,那么我们就弹出一个对话框询问用户是否确定要退出App,如果选择确定再退出,防止误触带来不好的体验。在1.0时的处理,非常不优雅,需要用到WillPopScope去包装,现在不需要了,直接在popRoute中即可处理。

classMyRouterDelegateextendsRouterDelegate<List<RouteSettings>>withChangeNotifier,PopNavigatorRouterDelegateMixin<List<RouteSettings>>{finalList<Page>_pages=[];@overridefinalGlobalKey<NavigatorState>navigatorKey=GlobalKey<NavigatorState>();@overrideWidgetbuild(BuildContextcontext){returnNavigator(key:navigatorKey,pages:List.of(_pages),onPopPage:_onPopPage,);}@overrideFuture<void>setNewRoutePath(List<RouteSettings>configuration)async{}@overrideFuture<bool>popRoute(){if(canPop()){_pages.removeLast();notifyListeners();returnFuture.value(true);}return_confirmExit();}boolcanPop(){return_pages.length>1;}bool_onPopPage(Routeroute,dynamicresult){if(!route.didPop(result))returnfalse;if(canPop()){_pages.removeLast();returntrue;}else{returnfalse;}}voidpush({requiredStringname,dynamicarguments}){_pages.add(_createPage(RouteSettings(name:name,arguments:arguments)));notifyListeners();}voidreplace({requiredStringname,dynamicarguments}){if(_pages.isNotEmpty){_pages.removeLast();}push(name:name,arguments:arguments);}MaterialPage_createPage(RouteSettingsrouteSettings){Widgetchild;switch(routeSettings.name){case'/home':child=constHome();break;case'/splash':child=constSplash();break;case'/login':child=constLogin();break;case'/details':child=Details(routeSettings.arguments!asMap<String,String>);break;default:child=constScaffold();}returnMaterialPage(child:child,key:Key(routeSettings.name!)asLocalKey,name:routeSettings.name,arguments:routeSettings.arguments,);}Future<bool>_confirmExit()async{finalresult=awaitshowDialog<bool>(context:navigatorKey.currentContext!,builder:(context){returnAlertDialog(content:constText('确定要退出App吗?'),actions:[TextButton(child:constText('取消'),onPressed:()=>Navigator.pop(context,true),),TextButton(child:constText('确定'),onPressed:()=>Navigator.pop(context,false),),],);});returnresult??true;}}用法深入

上述的案例中,我们还是没有解决Web版的问题。当我们在浏览器地址栏输入URL,无法定位到具体的路由页面;当我们切换到具体的路由页面,地址栏的URL也不会同步发生变化。如果你的应用将来要考虑兼容Web版,那么就很有必要继续深入学习Navigator2.0。

要想处理该问题,我们需要自定义一个路由信息解析器:

GestureDetector(onTap:(){Navigator.push(context,MaterialPageRoute(builder:(ctx)=>Details(_movieList![i].name,_movieList![i].imgUrl)));},child:Column(mainAxisSize:MainAxisSize.min,children:[Flexible(child:Image.network(_movieList![i].imgUrl)),Text(_movieList![i].name),],),);1

这里有两个方法需要实现,分别是

parseRouteInformation:帮助我们将一个URL地址转换成路由的状态(即配置信息)

restoreRouteInformation:帮助我们将路由的状态(配置信息)转换为一个URL地址

可见,这两个方法的功能正好相反,并且刚好对应我们的两个需求:输入URL切换相应路由页面;操作路由页面,URL同步变化。

具体而言,parseRouteInformation方法接收一个RouteInformation类型参数,它描述了一个URL的信息,它包含的两个属性分别是字符串location和动态类型state。location就是URL的path部分,state是用来保存页面中的状态的,例如页面中有一个输入框,并且输入框中输入了内容,保存到state中,下次恢复页面时,数据也可以得到恢复。弄清楚了这个方法的参数,上面的代码实现就很好理解了,我们将URL的path解析成Uri类型,这比直接操作字符串path要更方便,然后根据这些path信息,生成对应的路由配置RouteSettings并返回。

restoreRouteInformation方法的逻辑更加简单,它接收一组路由配置信息做参数,我需要根据当前的这些路由配置信息,组合生成一条URL,并封装成RouteInformation对象返回。这里返回的URL正是用于更新浏览器的地址栏的URL。

到这里,我们的路由信息解析器就写好了,现在需要在MyRouterDelegate中添加代码:

GestureDetector(onTap:(){Navigator.push(context,MaterialPageRoute(builder:(ctx)=>Details(_movieList![i].name,_movieList![i].imgUrl)));},child:Column(mainAxisSize:MainAxisSize.min,children:[Flexible(child:Image.network(_movieList![i].imgUrl)),Text(_movieList![i].name),],),);2

首先需要重写一个get方法currentConfiguration,其实现就是返回我们的Page列表,接着实现我们之前留空的setNewRoutePath方法。

前面在路由信息解析器中实现的parseRouteInformation被调用后,就会接着回调这里的setNewRoutePath方法,很明显,parseRouteInformation方法的返回值正是被转发到setNewRoutePath方法中的参数。我们在parseRouteInformation方法中完成了对URL的解析并生成了一组路由配置信息,现在这组配置信息被转发到了setNewRoutePath中,这意味着我们需要在setNewRoutePath中,将这组路由配置信息生成对应的Page对象,并插入到当前的Page列表,最终实现路由栈更新。整个流程概括成一句话,就是外部输入的一条URL,最终导致App内路由页面的生成和更新。

最后,修改main.dart,设置我们的路由信息解析器:

GestureDetector(onTap:(){Navigator.push(context,MaterialPageRoute(builder:(ctx)=>Details(_movieList![i].name,_movieList![i].imgUrl)));},child:Column(mainAxisSize:MainAxisSize.min,children:[Flexible(child:Image.network(_movieList![i].imgUrl)),Text(_movieList![i].name),],),);3

这里,我们直接替换MaterialApp提供的新构造方法router。使用该构造方法可以省略我们之前设置的RootBackButtonDispatcher。

切换App路由,地址栏也同步更新:

输入URL,App内导航到相应路由:

完美!完整源码访问nav2_demo

使用小结

实现RouterDelegate:

全网最简单的FlutterNavigator2.0路由指南

我们创建了Navigator作为路由的管理者,并设置了两个主要参数pages和onPopPage,其中pages是一个存放Page对象的列表;当路由被pop时,onPopPage会被回调,开发者可在此处理路由退栈的逻辑。 这里,我们真正需要关心的是,Page对象列表是什么东西? 我们知道,在Flutter中,使用路由这个词来表示App中的页面,路由栈也就是页面栈...

JTTI服务器

Jtti是一家新加坡全球网络基础服务商,为数百万个网站提供支持,提供香港服务器、新加坡服务器等多种全球服务器,自营全球多个数据中心,为用户提供优质的网络资源和服务。JTTI服务器整体性能是非常不错的,拥有CN2 GIA+BGP优化线路,多个节点可选,套餐配置支持自定义,经过第三方站长测评之后,获得了站长和客户的一致认可,无论是硬件性能,网络线路,还是带宽品质,都能够满足大陆用户的使用需求,以下是Jtti的服...

使用FlutterNavigator2.0最舒服的姿势

使用Navigator2.0注意事项我觉得目前RouterDelegate17是最舒服的使用Navigator2.0的姿势了,可能你还觉得不够,如果你要自己实现或修改RouterDelegate17的话,需要注意一些问题。Navigator的key要这样写 voidmain()async{//设置初始页面awaitrouterDelegate.setInitialPages([MaterialPage(child:PageA())]);run...

【Flutter 实战】动画序列、共享动画、路由动画

Hero( tag: 'sharedImage', child: /* 图片组件 */,)两个页面需设置相同的tag以实现动画效果。路由动画:PageRoute转场动画在Flutter中通过Navigator实现,如MaterialPageRoute和CupertinoPageRoute。自定义动画可通过PageRouteBuilder,如修改transitionsBuilder来定制。下面是一个自定义平移动画的例子:Page...

Flutter开发:Hero与HeroMode的使用

4.通过导航器将目标路由入栈来触发动画。Navigator推送和弹出操作会为每对hero配对,并在源路由和目标路由中使用匹配的标签触发hero动画。5.Flutter计算从起点到终点对hero界限进行动画处理的补间(生成每一帧大小和位置),并在叠加层中执行动画。根据要求及定义就可以直接撸代码了,很简单:首先是第一个...

在Flutter 添加页面过渡动画

对于预定义的路由:Navigator.pushNamed(context, '/pageTwo');Pushnamed (context,’/pageTwo’) ;Output:输出:[图片上传失败...(image-aaf4a9-1650550552943)]输出:[图片上传失败...(image-8bd8a1-1650550552943)]希望这个博客能帮助你深入了解 Flutter 的转变。谢谢阅读!如果有任何错误,请在评论中...

流动的观察者模式 | Flutter 设计模式

Dart中,Stream和ChangeNotifier是观察者模式的体现。Stream是异步数据传递的模型,而ChangeNotifier则在状态管理中扮演观察者角色,如Provider库中的CartModel,通过监听状态变化来驱动组件更新。在路由管理中,Flutter的Navigator通过RouteObserver实现对路由行为的观察,有助于理解用户行为。设计模式的使用需根据具体...

Flutter获取的OverlayState来自哪里?

尽管您可以直接创建一个[Overlay],但最常见的是使用[WidgetsApp]或[MaterialApp]中的[Navigator]创建的。导航器使用其overlay来管理路由的视觉外观。其中很关键的点在Navigator,那去找Navigator,顺便还找到了Route。Navigator中一层结构就是Overlay。那看下Flutter中导航到底是怎么做的。许多应用程序在其小...

Flutter弹框队列

方案一:监听Navigator路由动态变化 原理:当DialogQueue正在展示弹框时,将发生的didRemove行为及其目标pushRoute透传给业务方,由业务方来决定队列的下一步操作(清空队列或择机重弹)。也就是当DialogQueue当前正在展示的对话框被无情remove掉的时候,队列的按序弹框被强制中断,我们便允许业务方在合适的时候进行修复处理。

Flutter弹框队列

方案一:监听Navigator路由动态变化 原理:当DialogQueue正在展示弹框时,将发生的didRemove行为及其目标pushRoute透传给业务方,由业务方来决定队列的下一步操作(清空队列或择机重弹)。也就是当DialogQueue当前正在展示的对话框被无情remove掉的时候,队列的按序弹框被强制中断,我们便允许业务方在合适的时候进行修复处理。

声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。
E-MAIL:11247931@qq.com
现在去洛阳旅游合适吗,洛阳热天旅游攻略自驾游 洛阳夏天旅游景点推荐一下 急需解梦:这类梦好像不是我第一次梦到,只记得最后一段,我感觉到我的... ...一下灰尘和电脑辐射,是用隔离霜,BB霜,还是防晒霜? 梦到古战场就我一个人活着。 机战游戏我170级,吃经验补偿一下吃到182后就吃不上了,然后去打怪,经验... 机战远战机体170级除了重构外就没有别的办法快速升级吗? 求机战170级可以去180级去刷经验吗?那刷经杨比较快,要原创经验,谢谢... 阅读文言文,回答问题。 【甲】潭中鱼可百许头,皆若空游无所依, 日光下... 我的狗狗快生了? 求C语言题目解答(闪动的多彩圆) colors 2.0 find7怎么没有菜单键 find7 colors 2.0如何新建一个空白屏幕 中国民企,秒变顶级军火商? 军火商军火商 关于促进中小企业公共服务平台建设的指导意见服务平台建设的保障措施... 世界3大军火商国家,中国落选,美国仍旧霸主地位 国家中小企业公共服务示范平台 中望CAD和CAD有什么区别? 你好 我的手机是联想A500 手机内存满了 现在关机 开不了机 是不是手 ... 联想a500如何格式化 我的A500想刷机,系统内置文件太多,手机运行速度犹如乌龟,该怎样刷机... 如何让红包助手一直显示在状态栏上? 手机磁盘空间满了怎么删除,哪位帅哥能赐教?妹手机是联想A500 求一部那个超凡蜘蛛侠百度云资源,主演是那个演铁锯钢铁岭的那个小伙子... 联想a500手机内存老不足,动不动就自动退出,下载了一个虚拟内存,好象没... 求超凡蜘蛛侠百度云链接,有蜘蛛侠1-3的也顺便发一下。 怎么在巨潮资讯网上查看年报?如何看到 求电影!求超凡蜘蛛侠1双子幕百度云资源! 联想a500刚买回来占用太大 怎么治好颈椎病 男生说女生是他铁子什么意思? 颈椎病怎么自我治疗有效果?这手麻头晕的症状是要折磨死我啊! 在寄宿学校想家,有种想自杀的感觉,特压抑,每天都做梦梦到家里边,醒来后... 电动扶梯是如何伤人的 请问设置如何苹果6的来电名称显示两种语言出现?就是名字是中文 下面显 ... 手机拼图怎么拼图 我接到韩国长途电话,来电显示是+0085216...谁能告诉我能么回拨过去... 韩国打来的电话,手机来电显示的是+85216160000 ?? +82162069***是韩国的手机号么? 手机接了一个韩国的电话,多少钱一分钟?会不会无法显示来电号码的? ...后来使劲用力加重,造成前锁骨和肩部都有刺痛,有一个多月了,打了CT... 右侧锁骨上方疼痛怎么回事 右边脖子痛 锁骨旁边特别痛 按不到哪里痛 痛到整个右边的后脑勺 头都... 葛氏捏筋拍打疗法的脉位及手法有哪些? 白马藏族舞舞的舞蹈组合 白马藏族舞白马人简介 藏族舞蹈《羌姆》的宗教内涵与传承 《魁拔》小说共有几本 烤鱼怎样才能让它更香?
  • 焦点

最新推荐

猜你喜欢

热门推荐