Flutter开发 14. 命名路由、页面跳转、页面传参、异步编程
目录
1. 路由介绍2. MaterialApp - routes2.2 主页面的实现2.3 修改Login的实现2.3 配置命名路由2.4 添加路由跳转-pushNamed2.5 返回原来的页面-pop2.6 路由pop传值2.7 接收pop传值2.8 路由push传值2.9 接收push传值
3. 异步编程3.1 future3.2 async 和 await
4. 完整代码
1. 路由介绍
路由(Route)在移动应用开发中通常指页面(Page),这跟 Web 开发中单页应用的 Route 概念意义是相同的,Route 在 Android中 通常指一个 Activity,在 iOS 中指一个 ViewController。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。 Flutter 中的路由管理和原生开发类似,无论是 Android 还是 iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。–来源官方
2. MaterialApp - routes
前面基础知识中我们就已经提到过,在MaterialApp组件下,有一个routes属性, 这个属定的定义如下:
final Map
因为我们将要使用命名路由来实现页面间跳转,所以我们必须先提供一个路由表(routing table),这样应用程序才知道哪个名字与哪个页面相对应。 首先我们在pages目录下新建一个Home.dart文件,当做我们的主页面,用户可以在主页面中,实现跳转到登录页面,并获得登录的结果,登陆页面上节已经实现过 登录页面实现
2.2 主页面的实现
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State
}
class _HomeState extends State
String _strLoginInfo = "当前未登录状态";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("首页"),
),
body: SafeArea(
child: Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(0, 30, 0, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(_strLoginInfo),
ElevatedButton(
onPressed: () => {},
child: const Text("去登录"),
),
]),
),
));
}
这个页面功能非常简单,只是显示一行字,和一个按钮 在main.dart的main函数中加载主页面看一下效果
import 'package:first_flutter_demo/pages/Home.dart';
void main() {
runApp(MaterialApp(
home: const Home(),
));
}
运行效果如下:
2.3 修改Login的实现
为了每个页面显示不同的标题,我们将Scaffold加到build方法中.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("登录"),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
child: Column(children: [
TextField(
autofocus: true,
keyboardType: TextInputType.text,
controller: _textEditingControllerForName,
decoration: const InputDecoration(
labelText: "用户名",
hintText: "输入手机号或邮箱地址",
prefixIcon: Icon(Icons.person)),
),
TextField(
autofocus: false,
toolbarOptions: const ToolbarOptions(
copy: false,
cut: false,
paste: false,
selectAll: false), //不允许拷贝和粘贴
obscureText: true, //隐藏输入字符
obscuringCharacter: "#", //密码字符
controller: _textEditingControllerForPwd,
decoration: const InputDecoration(
labelText: "密码",
hintText: "输入登录密码",
prefixIcon: Icon(Icons.lock)),
),
Container(
margin: const EdgeInsets.only(top: 30),
height: 40,
width: double.infinity,
child: ElevatedButton(
onPressed: onClickLogin,
child: const Text("登录"),
),
),
Container(
margin: const EdgeInsets.only(top: 10),
height: 40,
width: double.infinity,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: const BorderSide(
width: 2,
color: Color.fromARGB(255, 0, 195, 255),
)),
onPressed: onClickRegist,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(
Icons.app_registration,
color: Color.fromARGB(255, 0, 195, 255),
),
Text(
"注册新账户",
style: TextStyle(
color: Color.fromARGB(255, 0, 195, 255),
),
)
],
),
),
),
const Expanded(flex: 1, child: SizedBox()),
const Text(
"Copyright: 爪爪课堂",
style: TextStyle(
color: Color.fromARGB(255, 138, 138, 138),
),
)
]),
),
),
);
}
}
2.3 配置命名路由
我们在main.dart的main函数中,为MaterialApp组件添加routes配置,key一个字符串名称 ,value是一个WidgetBuilder,格式如下:
typedef WidgetBuilder = Widget Function(BuildContext context);
我们先用最简单的方式来实现一下,只配置两个页面,一个是主页一个是登录页
void main() {
runApp(MaterialApp(
routes: {"login": (context) => Login(), "home": (context) => Home()},
home: const Home(),
));
}
这样配置后,我们就可以直接使用这个名字,来打开他对应的页面了
2.4 添加路由跳转-pushNamed
为Home.dart中的去登录按钮添加跳转事件.
//主页的去登录按钮事件
void onClickLogin() {
Navigator.of(context).pushNamed("login");
}
这里我们使用pushNamed方法,传入的字符串就是我们在MaterialApp-routes中定义的key, 这时我们点击去登录按钮时,就实现了跳转到登录页面的效果了。
2.5 返回原来的页面-pop
我们修改一下Login.dart页面的登录按钮事件.
//登录页面-登录按钮的点击事件
void onClickLogin() {
Navigator.of(context).pop();
}
实现起来非常简单,让当前页面出栈即可
2.6 路由pop传值
我们看一下pop方法的原型,可以发现他是有可选参数的:
@optionalTypeArgs
void pop
Navigator 还有很多其它方法,如Navigator.replace、Navigator.popUntil等 接下来我们将输入的用户名和密码通过这个pop参数传给Home页. 我们再次修改一下Login.dart页面的登录按钮事件.
//登录页面-登录按钮的点击事件
void onClickLogin() {
String stringLoginInfo = "用户名:" +
_textEditingControllerForName.text +
"; " +
"密码:" +
_textEditingControllerForPwd.text;
Navigator.of(context).pop(stringLoginInfo);
}
2.7 接收pop传值
我们先看一下pushNamed的原型:
Future
可以看到他是Future类型的返回值,也就是说他是一个异步操作,也许并不会马上得到返回值。 因此我们需要修改Home.dart中的去登录按钮添加跳转事件.
//去登录按钮的点击事件
Future
//通过路程命名路程由打开login页面,并等待返回结果
final args = await Navigator.of(context).pushNamed("login");
if (args != null) {
setState(() {
//将返回的登录信息更新到界面上
_strLoginInfo = args.toString();
});
}
}
这里我们使用了 await用来同步等待返回的结果. 这时我们执一下跳到登录页添好账号密码后,点击录就可以收到结果了。运行效果如下:
2.8 路由push传值
接下来我们做一下修改,当用户已经登录过后,下一次再进去登录页面,我们将上次的手机号默认帮他添好。首先修改Home.dart, 已登录状态时,现次点击去登录铵钮,将手机号码通过pushName的arguments传给Login页面.
//去登录按钮的点击事件
Future
int startPos = _strLoginInfo.indexOf(":");
int endPos = _strLoginInfo.indexOf(";");
String? strPhone;
if (startPos > 0 && endPos > 0) {
//如果已经是登录状态 截取出手机号码
strPhone = _strLoginInfo.substring(startPos + 1, endPos);
}
String? strRes;
//通过路程命名路程由打开login页面,并等待返回结果
if (strPhone != null) {
final args =
await Navigator.of(context).pushNamed("login", arguments: strPhone);
if (args != null) {
strRes = args.toString();
}
} else {
final args = await Navigator.of(context).pushNamed("login");
if (args != null) {
strRes = args.toString();
}
}
if (strRes != null) {
setState(() {
//将返回的登录信息更新到界面上
_strLoginInfo = strRes!;
});
}
}
2.9 接收push传值
修改Login.dart文件,将带参数的情况号码自动添到输入框中。
@override
Widget build(BuildContext context) {
Object? object = ModalRoute.of(context)?.settings.arguments;
if (object != null) {
_textEditingControllerForName.text = object.toString();
}
return Scaffold(
......
这时我们再测试的效果就是这样的。
3. 异步编程
异步操作可以让你的程序在等待一个操作完成时继续处理其它的工作。Dart 使用 Future 对象来表示异步操作的结果。你可以用 async 和 await 关键字或 Future 类的相关 API 来配合使用 future。
3.1 future
future是什么? future 是 Future 类的对象,其表示一个 T 类型的异步操作结果。如果异步操作不需要结果,则 future 的类型可为 Future。当一个返回 future 对象的函数被调用时,会发生两件事:
将函数操作列入队列等待执行并返回一个未完成的 Future 对象。
不久后当函数操作执行完成,Future 对象变为完成并携带一个值或一个错误。
当你写的代码依赖于 future 对象时,你有两种可选的实现方式:
使用关键字 async 和 await
使用 Future API
3.2 async 和 await
关键字 async 和 await 关键字 async 和 await 是 Dart 语言 异步支持 的一部分。它们允许你不使用 Future 的 API 编写看起来与同步代码一样的异步代码。异步函数 即在函数头中包含关键字 async 的函数。关键字 await 只能用在异步函数中。
4. 完整代码
main.dart
import 'package:first_flutter_demo/pages/Home.dart';
import 'package:first_flutter_demo/pages/Login.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
routes: {"login": (context) => Login(), "home": (context) => Home()},
home: const Home(),
));
}
Login.dart
import 'package:flutter/material.dart';
class Login extends StatefulWidget {
const Login({Key? key}) : super(key: key);
@override
State
}
class _LoginState extends State
final TextEditingController _textEditingControllerForName =
TextEditingController();
final TextEditingController _textEditingControllerForPwd =
TextEditingController();
@override
void initState() {
_textEditingControllerForName.addListener(() {
//可以监听输入变化
});
super.initState();
}
//登录按钮的点击事件
void onClickLogin() {
String stringLoginInfo = "用户名:" +
_textEditingControllerForName.text +
"; " +
"密码:" +
_textEditingControllerForPwd.text;
Navigator.of(context).pop(stringLoginInfo);
}
//注册按钮的点击事件
void onClickRegist() {}
@override
Widget build(BuildContext context) {
Object? object = ModalRoute.of(context)?.settings.arguments;
if (object != null) {
_textEditingControllerForName.text = object.toString();
}
return Scaffold(
appBar: AppBar(
title: const Text("登录"),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
child: Column(children: [
TextField(
autofocus: true,
keyboardType: TextInputType.text,
controller: _textEditingControllerForName,
decoration: const InputDecoration(
labelText: "用户名",
hintText: "输入手机号或邮箱地址",
prefixIcon: Icon(Icons.person)),
),
TextField(
autofocus: false,
toolbarOptions: const ToolbarOptions(
copy: false,
cut: false,
paste: false,
selectAll: false), //不允许拷贝和粘贴
obscureText: true, //隐藏输入字符
obscuringCharacter: "#", //密码字符
controller: _textEditingControllerForPwd,
decoration: const InputDecoration(
labelText: "密码",
hintText: "输入登录密码",
prefixIcon: Icon(Icons.lock)),
),
Container(
margin: const EdgeInsets.only(top: 30),
height: 40,
width: double.infinity,
child: ElevatedButton(
onPressed: onClickLogin,
child: const Text("登录"),
),
),
Container(
margin: const EdgeInsets.only(top: 10),
height: 40,
width: double.infinity,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: const BorderSide(
width: 2,
color: Color.fromARGB(255, 0, 195, 255),
)),
onPressed: onClickRegist,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(
Icons.app_registration,
color: Color.fromARGB(255, 0, 195, 255),
),
Text(
"注册新账户",
style: TextStyle(
color: Color.fromARGB(255, 0, 195, 255),
),
)
],
),
),
),
const Expanded(flex: 1, child: SizedBox()),
const Text(
"Copyright: 爪爪课堂",
style: TextStyle(
color: Color.fromARGB(255, 138, 138, 138),
),
)
]),
),
),
);
}
}
Home.dart
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State
}
class _HomeState extends State
String _strLoginInfo = "当前未登录状态";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("首页"),
),
body: SafeArea(
child: Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(0, 30, 0, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(_strLoginInfo),
ElevatedButton(
onPressed: onClickLogin,
child: const Text("去登录"),
),
]),
),
));
}
//去登录按钮的点击事件
Future
int startPos = _strLoginInfo.indexOf(":");
int endPos = _strLoginInfo.indexOf(";");
String? strPhone;
if (startPos > 0 && endPos > 0) {
//如果已经是登录状态 截取出手机号码
strPhone = _strLoginInfo.substring(startPos + 1, endPos);
}
String? strRes;
//通过路程命名路程由打开login页面,并等待返回结果
if (strPhone != null) {
final args =
await Navigator.of(context).pushNamed("login", arguments: strPhone);
if (args != null) {
strRes = args.toString();
}
} else {
final args = await Navigator.of(context).pushNamed("login");
if (args != null) {
strRes = args.toString();
}
}
if (strRes != null) {
setState(() {
//将返回的登录信息更新到界面上
_strLoginInfo = strRes!;
});
}
}
}
文章来源
发表评论