Flutter 导航教程
原文: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);
}
这个方法主要做了一下几个事情:
- push 一个新的 MaterialPageRout 到导航栈,这次增加了一个 bool 泛型参数。
- 在 push 新路由时,使用 await,这样它会一直等待直到这个路由被 pop 掉。
- 你所 push 的这个路由有一个 Column 用于显示两个带有手势检测器的 text widget。
- 点击 text widget 会触发 Navigator 将新路由 pop 出栈。
- 在调用 pop() 方法时,传递一个返回值,如果用户点击的是 OK 返回 true,否则返回 false。如果用户点击返回按钮,返回值为 null。
- 然后创建一个 AlertDialog,显示从路由返回的结果。
- 注意 AlertDialog 也必须 pop 出栈。
- 调用 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,),);}));
}
这里,你:
- push 一个 PageRouteBuild 入栈。
- 用 transitionDuration 指定时长。
- 用 pageBuilder 创建 MemberWidget 屏。
- 用 transitionsBuilder 属性创建新路由呈现时的渐入和旋转动画。
按 F5 进行 build & run,看一下新动画的样子:
噢,这真让我有点头晕!:]
接下来去哪里?
你可以从本教程头部或底部下载完整项目。
通过访问下列网址,你可以学习更多 Flutter 导航的知识:
- Flutter 文档中的路由及导航。
- Navigator API 文档。
在阅读文档时,请尤其注意阅读如何创建命名路由,这样你就可以调用 Navigator 的 pushNamed() 来调用路由了。
请继续关注更多的 Flutter 教程和屏播!
请在论坛或评论中提问,分享你的心得。希望你学得愉快!
Download Materials
Flutter 导航教程相关推荐
- Flutter 游戏教程之使用 Flutter 和 Flame 重现著名的 T-Rex 游戏
Flutter 游戏教程之使用 Flutter 和 Flame 重现著名的 T-Rex 游戏 这个想法 所以我把我的东西放在一起,开始想知道如何用 Flutter 制作游戏,最重要的是要做什么.由于我 ...
- 写给前端工程师的 Flutter 详细教程
本文作者:hicc,腾讯 CSIG 前端开发工程师 最爱折腾的就是前端工程师了,从 jQuery 折腾到 AngularJs,再折腾到 Vue.React.最爱跨屏的也是前端工程师,从 phonega ...
- flutter安装教程(win7)
本文是在安装flutter的时候,所遇到的问题参考的各个文档整理集合.此次是在win7上安装的问题记录.因为当初安装的时候针对win7的文档比较少,而且各个文档的解释比较散,本人遇到问题也是找了很久才 ...
- 【Flutter -- 基础组件】Flutter 导航栏
文章目录 1. TabBar 1.1 代码 1.2 效果图 2. BottomNavigationBar 2.1 构建底部标签 2.2 创建导航栏 2.3 效果图 1. TabBar Flutter ...
- Flutter 导航栏AppBar
恢弘志士之气,不宜妄自菲薄.--诸葛亮 People of noble ambition should not belittle themselves. zhugeliang 以上效果是谷歌 ...
- Vue + ElementUI 动态生成面包屑导航教程
在Web应用程序中,面包屑导航是一种常用的导航方式,它可以帮助用户更好地理解当前页面的位置和层次关系.在Vue项目中,结合ElementUI组件库,我们可以很容易地实现一个动态生成面包屑导航的功能.本 ...
- flutter 导航页面转换动画效果
引用:https://blog.csdn.net/whqwjb/article/details/87925588 main.dart: import 'package:flutter/material ...
- Android Studio使用Google Flutter完整教程
一套代码 iOS.Android 两端运行,Google Flutter 实在太强大.. "Flutter 可帮助你更容易.更快速的开发界面美观的移动应用." - - Goog ...
- html导航教程视频,导航_HTML+CSS前端基础知识教程_腾讯视频
更多资料源码请加3252897743第1天 html 1.HTTP协议 2.html是纯文本3.html骨架4.DTD文档类型5.head标签6.body标签7.html基本语法8.h系列的标 ...
最新文章
- 火出圈!河南大学教授毕业典礼金句频现:躺平得了初一,躺平不到十五!
- python3 如何给装饰器传递参数
- 《Objective-c》-(description方法)
- VS 2010 IDE 宏学习总结
- QT 中的 Graphics View 系统
- 阿里P8架构师谈:Quartz调度框架详解、运用场景、与集群部署实践
- jQuery框架学习第三天:如何管理jQuery包装集
- 参数幂等性校验失败_快速入手 Spring Boot 参数校验
- Windows 操作小技巧 之一(持续更新)
- onedrive不同版本
- OpenCL入门(一):简单概念
- H5 调用摄像头进行拍照
- 从五个维度来谈谈视觉设计师如何阐述设计风格
- 计算机实习生听课记录,实习生听课记录
- Ubuntu 20.04 源码编译Paddle2.2.2
- 好用的个微管理系统我知道
- JavaScript中的jQuery
- 有什么免费照片换发型软件?推荐几个换发型软件给你
- JS中 new FormData() - FormData对象的作用及用法
- 我将进化成一条狗(2)——大数据