zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Flutter之路由系列之Navigator简析

路由flutter 系列 简析 navigator
2023-09-14 09:06:42 时间

博主的博客Flutter之路由系列之LocalHistoryRoute简单的梳理下Flutter的路由机制,其中Navigator扮演者重要的角色。本篇博文就简单梳理下Navigator的相关知识点。闲言少叙,开始发车。
通过本篇博客你可以了解到:
1、MaterialApp内置了一个Navigator对象
2、一个APP中有多个Navigator对象在调用Navigator.of(context)要注意的事项

本篇通过一个demo app来说明Navigator的用法及其细节,在我们的demo中有个四个页面:InitPage,PageOne,PageTwo,DefaultPage。
其中PageOne,PageTow和DefalutPage三个页面及其简单,就是在屏幕总展示了“Page One”,“Page Two”和“Defalut Page”的文本文字,比如Default Page的页面效果如下:
在这里插入图片描述
在我本篇的demo中(博文最后提供全部源码),启动的时候会先展示InitPage,然后自动展示PageOne,点击PageOne,跳到PageTwo,点击PageTwo,跳到DefaultPage。关于这几个页面的关系,咱们一步步分析,先来看看demo的入口方法:

1、配置MaterialApp的初始路由和路由表

void main() {
  runApp(MyApp());///程序入口
}

 class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       initialRoute: '/initPage',
       routes: {
         '/': (BuildContext context) => DefaultPage(),
         '/initPage': (BuildContext context) => InitPage(),
       },
     );
   }
 }

关于MaterialApp有一个要注意的地方:它包含了APP中的top level 或者全局的Navigator对象,因为MaterialApp其内部内置了Navigator对象。另外MyApp还做了如下工作:

1、初始化了MaterialApp的initialRoute属性,该属性为String类型,代表着初始路由的Name
2、配置了MaterialApp的路由表routes属性,该属性是一个Map集合,key代表着路由的Name,Value代表着对应的Page

配置routes的时候,DefaultPage对应的路由名字是"/",代表默认路由;而InitPage对应的路由名字是“/initPage”,因为initialRoute属性设置为“/initPage”,所以我们的demo app运行起来的时候,首先展示的就是InitPage,所以来看看InitPage是什么。

注意此时内置的路由表的有两个路由,一个是默认路由“/”,一个是“/initPage”路由。

2、InitPage中Navigator使用方法

因为Navigator也是是个Widget,确切的来说是一个StatefulWidget.所以在InitPage中我们直接使用了Navigator作为UI展示,InitPage的代码如下:


 class InitPage extends StatelessWidget {
   @override
   Widget build(BuildContext context) {  
    ///直接返回了一个Navigator对象
     return Navigator(
       //初始路由的名字
       initialRoute: 'pageOne',
       onGenerateRoute: (RouteSettings settings) {
         WidgetBuilder builder;
         switch (settings.name) {
           case 'pageOne':
             //pageOne的路由对应的页面
             builder = (BuildContext ctx) => PageOne();
             break;
           case 'pageTwo':
              //pageTwo的路由对应的页面
             builder = (BuildContext ctx) => PageTwo(
               onSignupComplete: () {
               ///PageTwo点击事件,跳转到DefaultPage页面
               //注意这个Navigator.of操作的是MaterialApp的内置Navigator,后面会有说明
                 Navigator.of(context).pop();
               },
             );
             break;
         }
         return MaterialPageRoute(builder: builder, settings: settings);
     );
   }
 }

可以看出InitPage的build方法直接返回了Navigator,通过上面的的讲解我们知道MaterialApp也内置了一个Navigator对象,那么问题来了:
InitPage的Navigator对象和MaterialApp的Navigator对象,有啥区别和联系呢?这个问题会在后面的分析中说明,在这里读者可以留个心眼.

在InitPage 中我们为Navigator配置了如下属性:
1、initialRoute设置为pageOne
2、onGenerateRoute:根据路有配置RouteSettings 来展示不同的页面Page One 和Page Two,因为initialRoute配置为pageOne,所以APP 启动的时候就优先展示Page One.:
在这里插入图片描述
当我们调用Navigator.of(context).pushReplacementNamed(‘pageTwo’)或者Navigator.of(context).pushNamed(‘pageTwo’)的时候,就会走onGenerateRoute的 case 'pageTwo'分支:

   case 'pageTwo':
              //pageTwo的路由对应的页面
             builder = (BuildContext ctx) => PageTwo(
               onSignupComplete: () {
               ///PageTwo点击事件,跳转到DefaultPage页面
              //注意这个Navigator.of操作的是MaterialApp的内置Navigator,后面会有说明
                 Navigator.of(context).pop();
               },
             );
             break;

3、onGenerateRoute最终返回了MaterialPageRoute

2.1 PageOne的简单实现

再来看看PageOne的代码,PageOne页面展示了“Page One”文本和一个点击事件,当点击的时候跳转到PageTwo页面:


 class PageOne extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return DefaultTextStyle(
       child: GestureDetector(
         onTap: () {
           ///替换PageOne页面,展示PageTwo
           //注意这个Navigator.of操作的是InitPage的Navigator,后面会有说明
           Navigator.of(context)
             .pushNamed('pageTwo');
         },
         child: Text('Page One'),
       ),
     );
   }
 }

在PageOne中调用了Navigator.of(context).pushReplacementNamed('pageTwo')来展示Page Two,注意这个Navigator.of(Context)返回的对象是InitPage包含的那个Navigator对应的NavigatorState对象,而不是MaterialApp内置Navigator对应的NavigatorState对象

2.2 PageTwo的简单实现

PageTwo页面的主要功能是,点击页面的时候跳转到DefalutPage页面!但是其点击事件有个需要注意的地方,下面看源码:

 class PageTwo extends StatelessWidget {
   const PageTwo({
     this.onSignupComplete,
   });

   final VoidCallback onSignupComplete;

   @override
   Widget build(BuildContext context) {
     return GestureDetector(
       //点击事件
       onTap:onSignupComplete,
       child: DefaultTextStyle(
         child: Text('Page Two'),
       ),
     );
   }
 }

注意看上面PageTwo源码中build方法中的点击事件,我们是在InitPage中初始化的,代码如下(在这里称之为方式A):

///方式A
 case 'pageTwo':
             builder = (BuildContext ctx) => PageTwo(
             点击事件
               onSignupComplete: () {Navigator.of(context).pop();},
             );
             break

为什么不直接写成如下所示呢?(在这里称之为方式B):

//方式B
 Widget build(BuildContext context) {
     return GestureDetector(
       //点击事件
       onTap: () {Navigator.of(context).pop();},
       child: DefaultTextStyle(
         child: Text('Page Two'),
       ),
     );
   }

原因很简单,方式A和方式B的点击代码看起来完全一样都是执行Navigator.of(context).pop(),但是二者有着本质的区别:
方式A的Navigator.of(context)不等于方式B的Navigator.of(context)。更确切的来说是方式A的Navigator.of(context)返回的NavigatorState对象是属于MaterialApp内置的Navigator对象的,而方式B返回的Navigator.of(context)是属于InitPage包含的Navigator对象的,更直观的可以用下面代码来说明二者的区别:

在这里插入图片描述
如上图所以Navigator.of(context).pop(),是将InitPage弹出了路由,所以会展示DefaultPage.
而Navigator.of(ctx).pop()处理的是InitPage的路由逻辑,所以此时会将PageTwo弹出,而展示PageOne(因为前文说了在InitPage中先展示的是PageOne)。

到此为止Navigator的简单实用讲解完毕,如有不当之处,欢迎批评指正。下面是demo app的源码:

class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       title: 'Flutter Code Sample for Navigator',
       // MaterialApp contains our top-level Navigator
       initialRoute: '/initPage',
       routes: {
         '/': (BuildContext context) => DefaultPage(),
         '/initPage': (BuildContext context) => InitPage(),
       },
     );
   }
 }


class InitPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("context=="+context.toString());
    // SignUpPage builds its own Navigator which ends up being a nested
    // Navigator in our app.
    return Navigator(
      initialRoute: 'pageOne',
      onGenerateRoute: (RouteSettings settings) {
        WidgetBuilder builder;
        switch (settings.name) {
          case 'pageOne':
            builder = (BuildContext ctx) => PageOne();
            break;
          case 'pageTwo':
            builder = (BuildContext ctx) => PageTwo(
              onSignupComplete: () {
                Navigator.of(context).pop();
              },
            );
            break;
        }
        return MaterialPageRoute(builder: builder, settings: settings);
      },
    );
  }
}

 class DefaultPage extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     print("NavigatorpageTwo"+Navigator.of(context).toString());
     return DefaultTextStyle(
       style: Theme.of(context).textTheme.headline6,
       child: Container(
         color: Colors.white,
         alignment: Alignment.center,
         child: Text('Default  Page'),
       ),
     );
   }
 }

 class PageOne extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return DefaultTextStyle(
       style: Theme.of(context).textTheme.headline6,
       child: GestureDetector(
         onTap: () {
           print("Navigator"+Navigator.of(context).toString());
           Navigator.of(context)
             .pushNamed('pageTwo');
         },
         child: Container(
           color: Colors.lightBlue,
           alignment: Alignment.center,
           child: Text('Page One'),
         ),
       ),
     );
   }
 }

 class PageTwo extends StatelessWidget {
   const PageTwo({
     this.onSignupComplete,
   });

   final VoidCallback onSignupComplete;

   @override
   Widget build(BuildContext context) {
     return GestureDetector(
       onTap:onSignupComplete,
       child: DefaultTextStyle(
         style: Theme.of(context).textTheme.headline6,
         child: Container(
           color: Colors.pinkAccent,
           alignment: Alignment.center,
           child: Text('Page Two'),
         ),
       ),
     );
   }
 }