一,概述

移动应用通常通过成为‘屏幕’或者‘页面’的全屏元素显示其内容,在Flutter中,这些元素统称为路由,它们由导航器Navigator组件管理。导航器管理一组路由Route对象,并提供了管理堆栈的方法,例如Navigator.push和Navigator.pop。
如果类比这Ios记忆的话,你可以粗略地把一个路由对应到一个 UIViewController。Navigator的工作原理和 iOS 中 UINavigationController 非常相似,当你想跳转到新页面或者从新页面返回时,它可以 push() 和 pop() 路由。

在Flutter中,有两个主要的widget用于在页面之间导航:

    • Route是一个应用程序抽象的屏幕或页面;
    • Navigator 是一个管理路由的widget;

以上两种widget对应Flutter中实现页面导航的有两种选择:

    • 静态路由:具体指定一个由路由名构成的 Map。(MaterialApp
    • 动态路由:直接跳转到一个路由。(WidgetApp

二,页面跳转的两个选择

  Flutter中,跳转页面有两种方式:静态路由(又可称为命名路由)方式和动态路由(又可称为组件路由)方式。在Flutter管理多个页面有两个核心概念和类:RouteNavigator。一个route是一个屏幕或者页面的抽象,Navigator是管理routeWidgetNavigator可以通过route入栈出栈来实现页面之间的跳转。

  • 静态路由(又称命名路由,它是将应用中需要访问的每个页面命名为不重复的字符串,我们便可以通过这个字符串来将该页面实例推进路由)

    说明:'/ xxx' 表示 xxx页面,'/' 表示主页面。 这里的命名规范与 REST API 开发中的路由类似。 所以 '/' 通常表示的是我们的根页面。
    • 配置路由

      在原页面配置路由跳转,就是在MaterialApp里设置每个route对应的页面,注意:一个app只能有一个材料设计(MaterialApp),不然返回上一个页面会黑屏。
      代码如下:

      //入口页面
      class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
      return MaterialApp(
      //静态路由方式 配置初始路由
      initialRoute: '/',
      routes: {
      //默认走这个条件`/`
      '/':(BuildContext context){
      return HomeStateful();
      },
      //新页面路由
      '/mainnewroute':(BuildContext context){
      return new newRoute();
      }
      },
      //主题色
      theme: ThemeData(
      //设置为红色
      primarySwatch: Colors.red),
      //配置了初始路由,下面就不需要了
      //home: HomeStateful(),
      );
      }
      }

      因为配置了初始路由,所以home:HomeStateful就不用配置了。

    • 点击跳转
      //如果新页面不在同一个类中,记得把它导入
      import 'mainnewroute.dart';
      class HomeStateful extends StatefulWidget{
      @override
      State<StatefulWidget> createState(){
      return new HomeWidget();
      } } class HomeWidget extends State<HomeStateful> {
      @override
      Widget build(BuildContext context) {
      ...
      //Pointer
      Widget TestContainer = Listener(
      child:Container(
      width: 300.0,
      height: 300.0,
      color:Colors.red,
      child: RaisedButton(
      child: Text('点击我'),
      onPressed: (){
      //页面跳转方法
      Navigator.of(context).pushNamed('/mainnewroute');
      }),
      ),
      );
      return new Scaffold(
      appBar: new AppBar(
      title: new Text('Flutter Demo'),
      ),
      body:Center(
      child: TestContainer,
      ),
      );
      }
      }

      RaisedButton配置了点击方法,上面用了Navigator.of(context).pushNamed('/mainnewroute'),执行到这句,路由会找routes有没有配置/mainnewroute,有的话,就会根据配置跳到新的页面。

    • 配置新页面
      新页面,我在lib下建立一个新的文件(页面)mainfourday.dart,很简单:
      import 'package:flutter/material.dart';
      class newRoute extends StatelessWidget{ @override
      Widget build(BuildContext context){
      return HomeWidget();
      //注意:不需要MaterialApp
      // return MaterialApp(
      // theme: ThemeData(
      // //设置为hongse
      // primarySwatch: Colors.red),
      // home: HomeWidget(),
      // ); }
      } class HomeWidget extends StatelessWidget{
      @override
      Widget build(BuildContext context){
      return Scaffold(
      appBar: AppBar(
      title: Text('new Route'),
      ),
      body: Center(
      child:RaisedButton(
      child: Text('返回'),
      onPressed: (){
      //这是关闭页面
      Navigator.pop(context);
      }),
      // child: Text('这是新的页面'),
      ),
      );
      }
      }

      最终效果如下:

  • 动态路由(又称组件路由,要在堆栈上推送新的实例,我们可以调用导航器 Navigator.push ,传入当前 context 并且使用构建器函数创建 MaterialPageRoute 实例,该函数可以创建您想要在屏幕上显示的内容)

    下面说一下跳转页面的第二种方式,动态路由方式:

    child: RaisedButton(
    child: Text('点击我'),
    onPressed: (){
    //Navigator.of(context).pushNamed('/mainnewroute');
    //动态路由
    Navigator.push(
    context,
    MaterialPageRoute(builder: (newPage){
    return new newRoute();
    }),
    );
    }),

    代码效果和上面是一样的。

三,详解路由栈

前面,我们已经知道如何简单在路由栈中 push、pop 实例,然而,当遇到一些特殊的情况,这显然不能满足需求。Flutter 当然也有类似的可以解决各种业务需求的实现方式!

  • pushReplacementNamed 与 popAndPushNamed

    假如我们定义了四个静态路由,分别为"/Screen1","/Screen2","/Screen3","/Screen4"

RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, "/screen4");
},
child: Text("pushReplacementNamed"),
),
RaisedButton(
onPressed: () {
Navigator.popAndPushNamed(context, "/screen4");
},
child: Text("popAndPushNamed"),
),
      • 我们在 Screen3 页面使用 pushReplacementNamed与 popAndPushNamed方法 push 了 Screen4。
        此时路由栈情况如下:



                                                      Screen4 代替了 Screen3

      • pushReplacementNamed与 popAndPushNamed的区别在于:

         popAndPushNamed能够执行Screen2 弹出的动画与 Screen3 推进的动画而 pushReplacementNamed仅显示 Screen3 推进的动画。 
      • 案例:

pushReplacementNamed:当用户成功登录并且现在在 HomeScreen上时,您不希望用户还能够返回到 LoginScreen。因此,登录应完全由首页替换。另一个例子是从 SplashScreen转到 HomeScreen。 它应该只显示一次,用户不能再从 HomeScreen返回它。 在这种情况下,由于我们要进入一个全新的屏幕,我们可能需要借助此方法。

popAndPushNamed:假设您正在有一个 Shopping应用程序,该应用程序在 ProductsListScreen中显示产品列表,用户可以在 FiltersScreen中应用过滤商品。 当用户单击“应用筛选”按钮时,应弹出 FiltersScreen并使用新的过滤器值推回到 ProductsListScreen。 这里 popAndPushNamed显然更为合适。

  • pushNamedAndRemoveUntil

用户已经登陆进入 HomeScreen,然后经过一系列操作回到配合界面想要退出登录,你不能够直接 Push 进入 LoginScreen吧?你需要将之前路由中的实例全部删除,是的用户不会在回到先前的路由中。

    • pushNamedAndRemoveUntil 可实现该功能:

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

      这里的 (Route<dynamic> route) => false能够确保删除先前所有实例。

    • 现在又有一个需求:我们不希望删除先前所有实例,我们只要求删除指定个数的实例。

我们有一个需要付款交易的购物应用。在应用程序中,一旦用户完成了支付交易,就应该从堆栈中删除所有与交易或购物车相关的页面,并且用户应该被带到 PaymentConfirmationScreen,单击后退按钮应该只将它们带回到 ProductsListScreen或 HomeScreen

      

Navigator.of(context).pushNamedAndRemoveUntil('/screen4', ModalRoute.withName('/screen1'));

通过代码,我们推送 Screen4并删除所有路由,直到 Screen1

  

  • popUntil

    想象一下,我们在应用程序中要填写一系列信息,表单分布在多个页面中。假设需要填写三个页面的表单一步接着一步。 然而,在表单的第 3 部分,用户取消了填写表单。 用户单击取消并且应弹出所有之前与表单相关的页面,并且应该将用户带回 HomeScreen或者 DashboardScreen,这种情况下数据属于数据无效! 我们不会在这里推新任何新东西,只是回到以前的路由栈中。

Navigator.popUntil(context, ModalRoute.withName('/screen2'));
  • Popup routes(弹出路由)

    • 路由不一定要遮挡整个屏幕。 PopupRoutes 使用 ModalRoute.barrierColor覆盖屏幕,ModalRoute.barrierColor只能部分不透明以允许当前屏幕显示。 弹出路由是“模态”的,因为它们阻止了对下面其他组件的输入。

    • 有一些方法可以创建和显示这类弹出路由。 例如:showDialog,showMenu 和 showModalBottomSheet。 如上所述,这些函数返回其推送路由的 Future(异步数据,参考下面的数据部分)。 执行可以等待返回的值在弹出路由时执行操作。

    • 还有一些组件可以创建弹出路由,如 PopupMenuButton 和 DropdownButton。 这些组件创建 PopupRoute 的内部子类,并使用 Navigator 的push 和 pop 方法来显示和关闭它们。

  • 自定义路由

    您可以创建自己的一个窗口z组件库路由类(如 PopupRoute,ModalRoute 或 PageRoute)的子类,以控制用于显示路径的动画过渡,路径的模态屏障的颜色和行为以及路径的其他各个特性。

    PageRouteBuilder 类可以根据回调定义自定义路由。 下面是一个在路由出现或消失时旋转并淡化其子节点的示例。 此路由不会遮挡整个屏幕,因为它指定了opaque:false,就像弹出路由一样。

    Navigator.push(context, PageRouteBuilder(
    opaque: false,
    pageBuilder: (BuildContext context, _, __) {
    return Center(child: Text('My PageRoute'));
    },
    transitionsBuilder: (___, Animation<double> animation, ____, Widget child) {
    return FadeTransition(
    opacity: animation,
    child: RotationTransition(
    turns: Tween<double>(begin: 0.5, end: 1.0).animate(animation),
    child: child,
    ),
    );
    }
    ));
    • 路由两部分构成,“pageBuilder”和“transitionsBuilder”。

      该页面成为传递给 buildTransitions 方法的子代的后代。 通常,页面只构建一次,因为它不依赖于其动画参数(在此示例中以_和__表示)。 过渡是建立在每个帧的持续时间。

  • 嵌套路由

      一个应用程序可以使用多个路由导航器。将一个导航器嵌套在另一个导航器下方可用于创建“内部旅程”,例如选项卡式导航,用户注册,商店结帐或代表整个应用程序子部分的其他独立个体。

    iOS应用程序的标准做法是使用选项卡式导航,其中每个选项卡都维护自己的导航历史记录。因此,每个选项卡都有自己的导航器,创建了一种“并行导航”。

      除了选项卡的并行导航之外,还可以启动完全覆盖选项卡的全屏页面。例如:入职流程或警报对话框。因此,必须存在位于选项卡导航上方的“根”导航器。因此,每个选项卡的 Navigators 实际上都是嵌套在一个根导航器下面的Navigators。

      用于选项卡式导航的嵌套导航器位于 WidgetApp 和 CupertinoTabView中,因此在这种情况下您无需担心嵌套的导航器,但它是使用嵌套导航器的真实示例。

    以下示例演示了如何使用嵌套的 Navigator 来呈现独立的用户注册过程。

    尽管此示例使用两个 Navigators 来演示嵌套的 Navigators,但仅使用一个 Navigato r就可以获得类似的结果。

    class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    // ...some parameters omitted...
    // MaterialApp contains our top-level Navigator
    initialRoute: '/',
    routes: {
    '/': (BuildContext context) => HomePage(),
    '/signup': (BuildContext context) => SignUpPage(),
    },
    );
    }
    } class SignUpPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    // SignUpPage builds its own Navigator which ends up being a nested
    // Navigator in our app.
    return Navigator(
    initialRoute: 'signup/personal_info',
    onGenerateRoute: (RouteSettings settings) {
    WidgetBuilder builder;
    switch (settings.name) {
    case 'signup/personal_info':
    // Assume CollectPersonalInfoPage collects personal info and then
    // navigates to 'signup/choose_credentials'.
    builder = (BuildContext _) => CollectPersonalInfoPage();
    break;
    case 'signup/choose_credentials':
    // Assume ChooseCredentialsPage collects new credentials and then
    // invokes 'onSignupComplete()'.
    builder = (BuildContext _) => ChooseCredentialsPage(
    onSignupComplete: () {
    // Referencing Navigator.of(context) from here refers to the
    // top level Navigator because SignUpPage is above the
    // nested Navigator that it created. Therefore, this pop()
    // will pop the entire "sign up" journey and return to the
    // "/" route, AKA HomePage.
    Navigator.of(context).pop();
    },
    );
    break;
    default:
    throw Exception('Invalid route: ${settings.name}');
    }
    return MaterialPageRoute(builder: builder, settings: settings);
    },
    );
    }
    }

    Navigator.of 在给定 BuildContext 中最近的根 Navigator 上运行。 确保在预期的 Navigator 下面提供BuildContext,尤其是在创建嵌套 Navigators 的大型构建方法中。 Builder 组件可用于访问组件子树中所需位置的 BuildContext。

四,页面跳转发送数据

   页面跳转时有时需要发送数据到第二个页面,比如从订单列表到商品详情页面时,通常需要发送商品ID参数。

  • 动态路由传递参数

    Navigator.push(
    context,
    MaterialPageRoute(builder: (newPage){
    return new newRoute("这是一份数据到新页面");
    }),
    );

    在新页面改为如下:

    import 'package:flutter/material.dart';
    class newRoute extends StatelessWidget{
    //接收上一个页面传递的数据
    String str;
    //构造函数
    newRoute(this.str); @override
    Widget build(BuildContext context){
    return HomeWidget(str);
    }
    } class HomeWidget extends StatelessWidget{
    String newDate;
    HomeWidget(this.newDate); @override
    Widget build(BuildContext context){
    return Scaffold(
    appBar: AppBar(
    title: Text('new Route'),
    ),
    body: Center(
    child:RaisedButton(
    //显示上一个页面所传递的数据
    child: Text(newDate),
    onPressed: (){
    Navigator.pop(context);
    }),
    // child: Text('这是新的页面'),
    ),
    );
    }
    }
  • 静态路由传递参数

    静态路由方式传递参数,也就是在newRoute()加上所要传递的参数就可以了

    //新页面路由
    '/mainnewroute':(context){
    return new newRoute("sdsd");
    }

四,页面跳转返回数据

  • 数据传递
    在上面的大多数示例中,我们推送新路由时没有发送数据,但在实际应用中这种情况应用很少。 要发送数据,我们将使用 Navigator 将新的 MaterialPageRoute 用我们的数据推送到堆栈上(这里是 userName)。

    String userName = "John Doe";
    Navigator.push(
    context,
    new MaterialPageRoute(
    builder: (BuildContext context) =>
    new Screen5(userName)));

    要在 Screen5 中得到数据,我们只需在 Screen5 中添加一个参数化构造函数:

    class Screen5 extends StatelessWidget {
      final String userName;
    Screen5(this.userName);
    @override
    Widget build(BuildContext context) {
    print(userName)
    ...
    }
    }

    这表示我们不仅可以使用 MaterialPageRoute 作为 push 方法,还可以使用 pushReplacement ,pushAndPopUntil 等。基本上从我们描述的上述方法中路由方法,第一个参数现在将采用 MaterialPageRoute 而不是 namedRoute 的 String。

  • 数据返回
    我们可能还想从新页面返回数据。 就像一个警报应用程序,并为警报设置一个新音调,您将显示一个带有音频音调选项列表的对话框。 显然,一旦弹出对话框,您将需要所选的项目数据。 它可以这样实现:

    new RaisedButton(onPressed: ()async{
    String value = await Navigator.push(context, new MaterialPageRoute<String>(
    builder: (BuildContext context) {
    return new Center(
    child: new GestureDetector(
    child: new Text('OK'),
    onTap: () { Navigator.pop(context, "Audio1"); }
    ),
    );
    }
    )
    );
    print(value);
    },
    child: new Text("Return"),)

    在 Screen4 中尝试并检查控制台的打印值。

    另请注意:当路由用于返回值时,路由的类型参数应与 pop 的结果类型匹配。 这里我们需要一个 String 数据,所以我们使用了 MaterialPageRoute <String>。 不指定类型也没关系。

五,其他效果解释

  • maybePop

    如果我们在初始路由上并且有人错误地试图弹出这个唯一页面怎么办? 弹出堆栈中唯一的页面将关闭您的应用程序,因为它后面已经没有页面了。这显然是不好的体验。 这就是 maybePop() 起的作用。 点击 Screen1 上的 maybePop 按钮,没有任何效果。 在 Screen3 上尝试相同的操作,可以正常弹出。
    static Future<bool> maybePop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).maybePop<T>(result);
    } @optionalTypeArgs
    Future<bool> maybePop<T extends Object>([ T result ]) async {
    final Route<T> route = _history.last;
    assert(route._navigator == this);
    final RoutePopDisposition disposition = await route.willPop();
    if (disposition != RoutePopDisposition.bubble && mounted) {
    if (disposition == RoutePopDisposition.pop){
    pop(result);
    return true;
       }
    }
    return false;
    }
  • canPop(maybePop效果也可通过 canPop 实现:)

    static bool canPop(BuildContext context) {
    final NavigatorState navigator = Navigator.of(context, nullOk: true);
    return navigator != null && navigator.canPop();
    } bool canPop() {
    assert(_history.isNotEmpty);
    return _history.length > 1 || _history[0].willHandlePopInternally;
    }

    如果占中实例大于 1 或 willHandlePopInternally 属性为 true 返回 true,否则返回 false。
    我们可以通过判断 canPop 来确定是否能够弹出该页面。

  • 如何去除默认返回按钮

    AppBar({
    Key key,
    this.leading,
    this.automaticallyImplyLeading = true,
    this.title,
    this.actions,
    this.flexibleSpace,
    this.bottom,
    this.elevation = 4.0,
    this.backgroundColor,
    this.brightness,
    this.iconTheme,
    this.textTheme,
    this.primary = true,
    this.centerTitle,
    this.titleSpacing = NavigationToolbar.kMiddleSpacing,
    this.toolbarOpacity = 1.0,
    this.bottomOpacity = 1.0,
    }) : assert(automaticallyImplyLeading != null),
    assert(elevation != null),
    assert(primary != null),
    assert(titleSpacing != null),
    assert(toolbarOpacity != null),
    assert(bottomOpacity != null),
    preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),
    super(key: key);

    将 automaticallyImplyLeading置为 false

【Flutter学习】页面跳转之路由及导航的更多相关文章

  1. JavaWeb学习——页面跳转方式

    JavaWeb学习——页面跳转方式 摘要:本文主要学习了请求转发和响应重定向,以及两者之间的区别. 请求转发 相关方法 使用HttpServletRequest对象的 getRequestDispat ...

  2. 微信小程序~页面跳转和路由

    一个小程序拥有多个页面,我们可以通过wx.navigateTo推入一个新的页面,如图3-6所示,在首页使用2次wx.navigateTo后,页面层级会有三层,我们把这样的一个页面层级称为页面栈.

  3. 【微信小程序】Page页面跳转(路由/返回)并传参

    页面跳转的方法参考官方文档: https://mp.weixin.qq.com/debug/wxadoc/dev/framework/app-service/route.html 问题:使用wx.na ...

  4. 【Flutter学习】基本组件之AppBar顶部导航栏

    一,概述 AppBar 显示在app的顶部.AppBar包含5大部分,如下图: 二,构造函数及参数含义 构造函数 AppBar({ Key key, this.leading, //在标题前面显示的一 ...

  5. 【Flutter学习】基本组件之BottomNavigationBar底部导航栏

    一,概述 BottomNavigationBar即是底部导航栏控件,显示在页面底部的设计控件,用于在试图切换,底部导航栏包含多个标签.图标或者两者搭配的形式,简而言之提供了顶级视图之间的快速导航. 二 ...

  6. 【Flutter学习】基本组件之TabBar顶部导航

    一,概述 TabBar,是材料设计(Material design)中很常用的一种横向标签页.在Android原生开发中,我们常用ViewPage或者一些常用的标签页开源库,来实现并行界面的横向滑动展 ...

  7. React学习(3)——Router路由的使用和页面跳转

    React-Router的中文文档可以参照如下链接: http://react-guide.github.io/react-router-cn/docs/Introduction.html 文档中介绍 ...

  8. Flutter学习笔记(15)--MaterialApp应用组件及routes路由详解

    如需转载,请注明出处:Flutter学习笔记(15)--MaterialApp应用组件及routes路由详解 最近一段时间生病了,整天往医院跑,也没状态学东西了,现在是好了不少了,也该继续学习啦!!! ...

  9. Flutter学习六之实现一个带筛选的列表页面

    上期实现了一个网络轮播图的效果,自定义了一个轮播图组件,继承自StatefulWidget,我们知道Flutter中并没有像Android中activity的概念.页面见的跳转是通过路由从一个全屏组件 ...

随机推荐

  1. tomcat启动一闪而过处理

    进入tomcat安装目录(解压目录)下的bin目录,比如D:\Tomcat1\apache-tomcat-7.0.810\bin,打开startup.bat文件,在最上面加上下面两句: SET JAV ...

  2. ThinkPHP整合datatables实现服务端分页

    最近做东西有一个需求,因为数据量很大,在这里我决定使用datatables的服务端分页,同时还需要传递查询条件到服务端.在网上搜索的大部分文章都感觉有些误差,于是自己封装了一下,主要配置/工具为: 服 ...

  3. python练习题自己实现一个字符串的find函数

    # 第五题:自己实现一个字符串的find函数 # 1.在一个字符串中查找另一个字符串 # 2.找到了返回第一次出现的位置 # 3.没找到返回-1 # 4.参数s1为源字符串,参数s2为要查找的字符串 ...

  4. php开发面试题---禁用cookie之后,如何使用session

    php开发面试题---禁用cookie之后,如何使用session 一.总结 一句话总结: 在每个url后面自动加上PHPSESSID的值即可,用户禁止cookie后,服务器仍会将sessionId以 ...

  5. wsl和windows相互访问文件夹

    How to access Windows folders from Bash on Ubuntu on Windows You'll find the Windows C:\ structure a ...

  6. php7.0 新增运算符??

    ??是php7 新增符号 其作用近似于三目运算符 ?: 但存在着细微差别 比较示例代码如图:         $b = $a?$a:2; 三目运算 <=>     $e = $a??'ho ...

  7. 使用Python的PIL模块来进行图片对比

    使用Python的PIL模块来进行图片对比 在使用google或者baidu搜图的时候会发现有一个图片颜色选项,感觉非常有意思,有人可能会想这肯定是人为的去划分的,呵呵,有这种可能,但是估计人会累死, ...

  8. selenium,webdriver爬取斗鱼主播信息 实操

    from selenium import webdriver import time from bs4 import BeautifulSoup class douyuSelenium(): #初始化 ...

  9. Java并发Lock接口

    java.util.concurrent.locks.Lock接口用作线程同步机制,类似于同步块.新的锁定机制更灵活,提供比同步块更多的选项. 锁和同步块之间的主要区别如下: 序列的保证 - 同步块不 ...

  10. Cocos2d-x之Menu

    |   版权声明:本文为博主原创文章,未经博主允许不得转载. cocos2d-x菜单简介: 菜单也是游戏开发中的重要环节,一般游戏开始的第一个画面都是游戏主菜单,这些菜单包括,开始游戏,游戏设置,关卡 ...