原文:Flutter Navigation Tutorial
作者:Joe Howard
译者:kmyhy

比只有一屏的 app 更好的是什么?当然是有两屏的 app 了:]

导航是移动 app UX 的重要组成部分。由于手机屏幕资源有限,用户需要不停地在各个屏幕之间进行导航,例如,从一个表格导航到详情屏幕,从购物车导航到结算屏,从菜单导航到表单,等等。一个良好的导航能帮助用户不迷失方向并在宽广的 app 中进退自如。

iOS 的导航通常是哟 UINavigationController,这是一个栈式的屏幕切换方式。在 Android 中,则主要用 activity 栈为用户导航。在这些栈中,两个屏幕之间的动画是不一样的,从而导致 app 风格不一。

和原生 SDK 一样,跨平台开发框架也为 app 提供了屏幕切换方式。大部分情况下,你想让每个平台上的导航方式和用户预期的保持一致。

Flutter 是一个 Google 的跨平台开发 SDK,允许你基于同一套代码快速创建 iOS 和 Android app。如果你没有接触过 Flutter,请阅读我们的 Flutter 开始教程,以了解基本的 Flutter 使用。

在本教程中,你将了解 Flutter 如何在一个跨平台 app 中实现屏幕间的导航,包括:

  • 路由和导航
  • 从栈中弹出
  • 从路由中返回值
  • 自定义导航动画

开始

你可以在本文头部或底部下载开始项目。
本教程将使用安装了 Flutter 扩展的 VSCode。你也可以用 IntelliJ IDEA 或 Android Studio 或任意文本编辑工具并在命令行中使用 Flutter。

在 VSCode 中选择 File > Open 并找到开始项目解压后的根文件夹,打开开始项目:

VSCode 会提示下载项目要用到的包,请根据提示进行:

项目打开后,按 F5 ,build & run。如果 VSCode 提示你选择 app 执行环境,请选择 Dart & Flutter:

这是在 iOS 模拟器中运行项目:

这是在 Android 模拟器中运行项目:

“slow mode” 标志显示,表示你正在以 debug 模式运行 app。

开始 app 显示了一个 GitHub 组织的成员列表。在本教程中,我们将从这个第一屏导航到每个成员单独的屏幕。

第二屏

首先要创建针对每个成员的屏幕画面。每个 Flutter UI 的元素都是 UI widget,因此我们需要创建 member widget。

首先,右键点击项目中的 lib 文件夹,选择 New File,创建一个新文件,名为 memberwidget.dart:

添加导入语句,添加一个 StatefulWidget 子类,名为 MemberWidget:

import 'package:flutter/material.dart';import 'member.dart';class MemberWidget extends StatefulWidget {final Member member;MemberWidget(this.member)  {if (member == null) {throw new ArgumentError("member of MemberWidget cannot be null. ""Received: '$member'");}}@overridecreateState() => new MemberState(member);
}

MemberWidget 用 MemberState 类来保存它的状态,并传递一个 Member 对象给 MemberState。在 widget 的构造函数中,确保 member 参数不为空。

在同一文件中 MemberWidget 的上面添加一个 MemberState 类:

class MemberState extends State<MemberWidget> {final Member member;MemberState(this.member);
}

这里声明了一个 Member 属性和一个构造函数。

每个 widget 都必须重写 build() 方法,因此在 MemberState 中重写该方法:

@override
Widget build(BuildContext context) {return new Scaffold (appBar: new AppBar(title: new Text(member.login),),body: new Padding(padding: new EdgeInsets.all(16.0),child: new Image.network(member.avatarUrl)));
}

这里创建了一个 Scaffold,即材料设计的容器,它包含了一个 AppBar 和一个child 为成员头像 Image 的 Padding widget。

成员的屏幕已经写好,接下来可以进行导航了:]

路由

在 Flutter 中导航是基于路由概念的。

路由就好比 REST API 中的路由概念,每个路由都是相对于根的。app 中的 main() 方法所创建的 widget 就是根。

一种使用路由的方法就是 PageRoute 类。因为当前 app 是一个 Flutter Material App,你需要用的是 MaterialPageRoute 子类。

在 GHFlutterState 头部加入 import 语句,以便能够调用 member widget:

import 'memberwidget.dart';

然后在 ghflutterwidget.dart 中为 GHFlutterState 添加私有方法:

_pushMember(Member member) {Navigator.of(context).push(new MaterialPageRoute(builder: (context) => new MemberWidget(member)));
}

这里用 Navigator 来 push 一个 MaterialPageRoute 到导航栈中,而这个 MaterialPageRoute 用你的 MemberWidget 进行构造。

现在的用户点击表格行时需要调用 _pushMemeber()。你需要修改 GHFlutterState 中的 _buildRow() 方法,在 ListTile 中添加一个 onTap 属性:

Widget _buildRow(int i) {return new Padding(padding: const EdgeInsets.all(16.0),child: new ListTile(title: new Text("${_members[i].login}", style: _biggerFont),leading: new CircleAvatar(backgroundColor: Colors.green,backgroundImage: new NetworkImage(_members[i].avatarUrl)),// Add onTap here:onTap: () { _pushMember(_members[i]); },));
}

当行被点击,_pushMember() 方法被调用,同时传递所选中的 member。

按 F5 build & run。点击某一行,你会看到成员详情屏显示:

这是在 iOS 中运行的效果:

注意,Android 中的返回按钮是 Android 风格的,而 iOS 中的返回按钮是 iOS 风格的,屏幕转换动画和对应平台保持一致。

点击返回按钮,回到列表页,如果你想用在 app 中用你自己的按钮来触发返回该怎么做?

弹出

因为在 Flutter app 中的导航使用栈,同时你已经 push 了一个新的屏幕 widget 到栈中,那么为了返回上一屏,你必须对栈进行 pop 操作。

修改 MemberState 的 build() 方法,添加一个 IconButton,将 Image 替换成一个 Column widget:

@override
Widget build(BuildContext context) {return new Scaffold (appBar: new AppBar(title: new Text(member.login),),body: new Padding(padding: new EdgeInsets.all(16.0),// Add Column here:child: new Column(children: [new Image.network(member.avatarUrl),new IconButton(icon: new Icon(Icons.arrow_back, color: Colors.green, size: 48.0),onPressed: () { Navigator.pop(context); }),]),));
}

为了将 Image 和 IconButton 垂直布局,你添加了一个 Column。对于 IconButton,你将 onPressed 属性设置为调用 Navigator 来对栈进行 pop。

按 F5 build & run,你可以点击新加的返回箭头来回到成员列表:

返回值

路由也可以有返回值,就像 Android 使用 onActivityResult() 来读取结果一样。

来看个简单例子,在 MemberState 中添加下列私有的异步方法:

_showOKScreen(BuildContext context) async {// 1, 2bool value = await Navigator.of(context).push(new MaterialPageRoute<bool>(builder: (BuildContext context) {return new Padding(padding: const EdgeInsets.all(32.0),// 3child: new Column(children: [new GestureDetector(child: new Text('OK'),// 4, 5onTap: () { Navigator.of(context).pop(true); }),new GestureDetector(child: new Text('NOT OK'),// 4, 5onTap: () { Navigator.of(context).pop(false); })]));}));// 6var alert = new AlertDialog(content: new Text((value != null && value) ? "OK was pressed" : "NOT OK or BACK was pressed"),actions: <Widget>[new FlatButton(child: new Text('OK'),// 7onPressed: () { Navigator.of(context).pop(); })],);// 8showDialog(context: context, child: alert);
}

这个方法主要做了一下几个事情:

  1. push 一个新的 MaterialPageRout 到导航栈,这次增加了一个 bool 泛型参数。
  2. 在 push 新路由时,使用 await,这样它会一直等待直到这个路由被 pop 掉。
  3. 你所 push 的这个路由有一个 Column 用于显示两个带有手势检测器的 text widget。
  4. 点击 text widget 会触发 Navigator 将新路由 pop 出栈。
  5. 在调用 pop() 方法时,传递一个返回值,如果用户点击的是 OK 返回 true,否则返回 false。如果用户点击返回按钮,返回值为 null。
  6. 然后创建一个 AlertDialog,显示从路由返回的结果。
  7. 注意 AlertDialog 也必须 pop 出栈。
  8. 调用 showDialog() 显示这个 alert。

上面需要注意的是 MaterialPageRoute 中的类型参数 bool,你可以将它替换成你想从路由中返回的任意类型,同时你需要将返回值在调用 pop 时传入,例如 Navigator.of(context).pop(true)。

修改 MemberState 的 build() 方法,添加一个 RaisedButton 按钮来调用 _showOKScreen():

@override
Widget build(BuildContext context) {return new Scaffold (appBar: new AppBar(title: new Text(member.login),),body: new Padding(padding: new EdgeInsets.all(16.0),child: new Column(children: [new Image.network(member.avatarUrl),new IconButton(icon: new Icon(Icons.arrow_back, color: Colors.green, size: 48.0),onPressed: () { Navigator.pop(context); }),// Add RaisedButton here:new RaisedButton(child: new Text('PRESS ME'),onPressed: () { _showOKScreen(context); })]),));
}

这个 RaiseButton 用于显示新屏幕。

按 F5 开始 build & run。点击 PRESS ME 按钮,然后点 OK 或者 NOT OK 或者返回按钮。你会从新屏幕中看到用户点击的结果:

自定义动画

为了给你的 app 的导航添加一点独特的味道,你可以自定义转换动画。你可以扩展 PageRoute 类,也可以用 PageRouteBuilder 之类的类通过回调方式自定义路由。

修改 GHFlutterState 的 _pushMember 方法,push 一个 PageRouteBuilder 入栈:

_pushMember(Member member) {// 1Navigator.of(context).push(new PageRouteBuilder(opaque: true,// 2transitionDuration: const Duration(milliseconds: 1000),// 3pageBuilder: (BuildContext context, _, __) {return new MemberWidget(member);},// 4transitionsBuilder: (_, Animation<double> animation, __, Widget child) {return new FadeTransition(opacity: animation,child: new RotationTransition(turns: new Tween<double>(begin: 0.0, end: 1.0).animate(animation),child: child,),);}));
}

这里,你:

  1. push 一个 PageRouteBuild 入栈。
  2. 用 transitionDuration 指定时长。
  3. 用 pageBuilder 创建 MemberWidget 屏。
  4. 用 transitionsBuilder 属性创建新路由呈现时的渐入和旋转动画。

按 F5 进行 build & run,看一下新动画的样子:

噢,这真让我有点头晕!:]

接下来去哪里?

你可以从本教程头部或底部下载完整项目。

通过访问下列网址,你可以学习更多 Flutter 导航的知识:

  • Flutter 文档中的路由及导航。
  • Navigator API 文档。

在阅读文档时,请尤其注意阅读如何创建命名路由,这样你就可以调用 Navigator 的 pushNamed() 来调用路由了。

请继续关注更多的 Flutter 教程和屏播!

请在论坛或评论中提问,分享你的心得。希望你学得愉快!

Download Materials

Flutter 导航教程相关推荐

  1. Flutter 游戏教程之使用 Flutter 和 Flame 重现著名的 T-Rex 游戏

    Flutter 游戏教程之使用 Flutter 和 Flame 重现著名的 T-Rex 游戏 这个想法 所以我把我的东西放在一起,开始想知道如何用 Flutter 制作游戏,最重要的是要做什么.由于我 ...

  2. 写给前端工程师的 Flutter 详细教程

    本文作者:hicc,腾讯 CSIG 前端开发工程师 最爱折腾的就是前端工程师了,从 jQuery 折腾到 AngularJs,再折腾到 Vue.React.最爱跨屏的也是前端工程师,从 phonega ...

  3. flutter安装教程(win7)

    本文是在安装flutter的时候,所遇到的问题参考的各个文档整理集合.此次是在win7上安装的问题记录.因为当初安装的时候针对win7的文档比较少,而且各个文档的解释比较散,本人遇到问题也是找了很久才 ...

  4. 【Flutter -- 基础组件】Flutter 导航栏

    文章目录 1. TabBar 1.1 代码 1.2 效果图 2. BottomNavigationBar 2.1 构建底部标签 2.2 创建导航栏 2.3 效果图 1. TabBar Flutter ...

  5. Flutter 导航栏AppBar

     恢弘志士之气,不宜妄自菲薄.--诸葛亮 People of noble ambition should not belittle themselves.     zhugeliang 以上效果是谷歌 ...

  6. Vue + ElementUI 动态生成面包屑导航教程

    在Web应用程序中,面包屑导航是一种常用的导航方式,它可以帮助用户更好地理解当前页面的位置和层次关系.在Vue项目中,结合ElementUI组件库,我们可以很容易地实现一个动态生成面包屑导航的功能.本 ...

  7. flutter 导航页面转换动画效果

    引用:https://blog.csdn.net/whqwjb/article/details/87925588 main.dart: import 'package:flutter/material ...

  8. Android Studio使用Google Flutter完整教程

    一套代码 iOS.Android 两端运行,Google Flutter 实在太强大.. "Flutter 可帮助你更容易.更快速的开发界面美观的移动应用."  - -  Goog ...

  9. html导航教程视频,导航_HTML+CSS前端基础知识教程_腾讯视频

    更多资料源码请加3252897743第1天 html   1.HTTP协议   2.html是纯文本3.html骨架4.DTD文档类型5.head标签6.body标签7.html基本语法8.h系列的标 ...

最新文章

  1. 火出圈!河南大学教授毕业典礼金句频现:躺平得了初一,躺平不到十五!
  2. python3 如何给装饰器传递参数
  3. 《Objective-c》-(description方法)
  4. VS 2010 IDE 宏学习总结
  5. QT 中的 Graphics View 系统
  6. 阿里P8架构师谈:Quartz调度框架详解、运用场景、与集群部署实践
  7. jQuery框架学习第三天:如何管理jQuery包装集
  8. 参数幂等性校验失败_快速入手 Spring Boot 参数校验
  9. Windows 操作小技巧 之一(持续更新)
  10. onedrive不同版本
  11. OpenCL入门(一):简单概念
  12. H5 调用摄像头进行拍照
  13. 从五个维度来谈谈视觉设计师如何阐述设计风格
  14. 计算机实习生听课记录,实习生听课记录
  15. Ubuntu 20.04 源码编译Paddle2.2.2
  16. 好用的个微管理系统我知道
  17. JavaScript中的jQuery
  18. 有什么免费照片换发型软件?推荐几个换发型软件给你
  19. JS中 new FormData() - FormData对象的作用及用法
  20. 我将进化成一条狗(2)——大数据

热门文章

  1. 初学者最系统的前端学习之路
  2. python一键获取豆瓣租房小组前十页信息,并导入EXCEL(Xpath法)
  3. 51单片机程序开发入门知识
  4. 抽象工厂模式学习总结
  5. ups电路图集下载_现代UPS电源电路图集
  6. QToolButton的Checked问题及解决方法
  7. Android.mk使用
  8. 倒三角形(triangle)(C++)
  9. LKY_OfficeTools 一键优雅的安装并激活你的Office
  10. 直播 | 对话V神 以太坊核心研究者:ETH2.0 蓄势待发,我们应该如何参与?