页面导航

RaisedButton

  • RaisedButton这个组件的使用在实际工作中用的也比较多,它有两个最基本的属性
    • child:可以放入容器,图标,文字。让你构建多彩的按钮
    • onPressed:点击事件的相应,一般会调用Navigator组件
  • Navigator.push:是跳转到下一个页面,它要接受两个参数一个是上下文context,另一个是要跳转的处理函数
  • Navigator.pop:是返回到上一个页面,使用时传递一个context(上下文)参数,使用时要注意的是,你必须是有上级页面的,也就是说上级页面使用了Navigator.push
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import 'package:flutter/material.dart';

void main() => runApp(
MaterialApp(
title: '导航演示01',
home: new FirstScreen()
)
);

class FirstScreen extends StatelessWidget{

@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(title:Text('导航页面')),
body: Center(
child: RaisedButton(
child: Text('查看商品详情页'),
onPressed: (){
Navigator.push(context,MaterialPageRoute(
builder: (context) => new SecondScreen()
));
},
),
),
);
}
}

// 新的页面
class SecondScreen extends StatelessWidget{
@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(title: Text('商品详情页')),
body:Center(
child: RaisedButton(
child: Text('返回'),
onPressed:(){
Navigator.pop(context);
}
)
)
);
}
}
  • 组件之间跳转,比如新建一个otherPage.dart,里面写登陆和菜单页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import 'package:flutter/material.dart';

class LoginPage extends StatelessWidget{

@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text('登陆')
),
body: RaisedButton(
onPressed:(){
Navigator.of(context).push(
MaterialPageRoute(
builder: (context){
return MenuPage();
},
// 设置路由名称 参数,这种跳转是否为首页,首页没有返回按钮
settings: RouteSettings(
name: 'menu',
arguments: '' // 参数
),
// 覆盖后的路由释放资源
maintainState: false,
// 是否为全屏
fullscreenDialog: true
));
},
child: Text('登陆')
)
);
}
}

class MenuPage extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('菜单')
)
);
}
}
  • 然后在main.dart中引入一个登录页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_demo/LoginWidget.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 去掉右上角debug
debugShowCheckedModeBanner: false,
home: Scaffold(
body: LoginPage(),
),
);
}
}

参数传递

  • 代码比较多,可以使用VSCode 中的Awesome Flutter snippets插件。它可以帮忙我们快速生成常用的Flutter代码片段
  • 比如输入stlss就会给我们生成如下代码
1
2
3
4
5
6
7
8
9
10
class name extends StatelessWidget {
const name({ Key? key }) : super(key: key);

@override
Widget build(BuildContext context) {
return Container(

);
}
}

路由参传

  • 路由参数配置在settings: RouteSettings()里面,然后通过以下代码接收
1
dynamic arguments = ModalRoute.of(context)?.settings.arguments;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import 'package:flutter/material.dart';

class LoginPage extends StatelessWidget{

@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text('登陆')
),
body: RaisedButton(
onPressed:() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context){
return MenuPage(
title: '菜单123'
);
},
// 设置路由名称 参数,这种跳转是否为首页,首页没有返回按钮
settings: RouteSettings(
name: 'menu',
arguments: '传的参数' // 参数 也可以传json {"name": "menu"}
),
// 覆盖后的路由释放资源
maintainState: false,
// 是否为全屏
fullscreenDialog: true
))
.then((value) => print(value)); // 有返回值就执行
},
child: Text('登陆')
)
);
}
}

class MenuPage extends StatelessWidget {
final String title;

const MenuPage({Key? key,required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
// 接收参数
dynamic arguments = ModalRoute.of(context)?.settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text(title+""+arguments),
// title: Text(title+""+arguments.toString()) // JSON字符要处理
),
body: RaisedButton(
onPressed:(){
Navigator.of(context).pop({
"username":"admin"
});
},
child:Text("返回")
),
);
}
}

组件传参

  • 两个组件之间传参直接在括号中传即可,然后再接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import 'package:flutter/material.dart';

class LoginPage extends StatelessWidget{

@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text('登陆')
),
body: RaisedButton(
onPressed:() async{
var result = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context){
// 传递参数
return MenuPage(
title: '菜单123'
);
},
// 设置路由名称 参数,这种跳转是否为首页,首页没有返回按钮
settings: RouteSettings(
name: 'menu',
arguments: '' // 参数
),
// 覆盖后的路由释放资源
maintainState: false,
// 是否为全屏
fullscreenDialog: true
));
print(result);
},
child: Text('登陆')
)
);
}
}

class MenuPage extends StatelessWidget {
final String title;
// 接收参数
const MenuPage({Key? key,required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title)
),
body: RaisedButton(
onPressed:(){
Navigator.of(context).pop({
"username":"admin"
});
},
child:Text("返回")
),
);
}
}

商品案例

  • Dart中可以使用类来抽象一个数据,比如我们模仿一个商品信息,有商品标题和商品描述。我们定义了一个Product类,里边有两个字符型变量,titledescription
1
2
3
4
5
class Product{
final String title; //商品标题
final String description; //商品描述
Product(this.title,this.description);
}
  • 作一个商品的列表,这里我们采用动态的构造方法,在主方法里传递一个商品列表List到自定义的Widget中;这个组件我们传递了一个products参数,也就是商品的列表数据,这个数据是我们用List.generate生成的。并且这个生成的List原型就是我们刚开始定义的Product这个类(抽象数据)
1
2
3
4
5
6
7
8
void main() {
runApp(MaterialApp(
title: '数据传递案例',
home: ProductList(
// 传递一个products数组
products: List.generate(20, (i) => Product('商品 $i', '这是一个商品详情,编号为:$i')),
)));
}
  • ProductList自定义组件的代码;先接受了主方法传递过来的参数,接受后用ListView.builder方法,作了一个根据传递参数数据形成的动态列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ProductList extends StatelessWidget {
// 声明接收的参数
final List<Product> products;
ProductList({Key? key, required this.products}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('商品列表')),
// 动态生成列表
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return ListTile(title: Text(products[index].title), onTap: () {});
},
));
}
}

  • 我们还是使用Navigator组件,然后使用路由MaterialPageRoute传递参数,具体代码如下,这段代码要写在onTap响应事件当中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ProductList extends StatelessWidget {
final List<Product> products;
const ProductList({ Key? key, required this.products}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('商品列表')),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(products[index].title),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder:(context)=>new ProductDetail(product:products[index])
)
);
});
},
));
}
}
  • 现在需要声明ProductDetail这个类(组件),先要作的就是接受参数,具体代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ProductDetail extends StatelessWidget {

// 接收参数
final Product product;
ProductDetail({Key ? key ,required this.product}):super(key:key);

@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title:Text('${product.title}'),
),
body:Center(child: Text('${product.description}'),)
);
}
}

返回数据

  • 当我们返回页面时返回结果到上一个页面(也就是父页面)。这样的场景经常用于,我们去子页面选择了一项选项,然后把选择的结果返回给父级页面

异步编程

  • Future对象表示异步操作的结果,我们通常通过then()来处理返回的结果
  • async用于标明函数是一个异步函数,其返回值类型是Future类型
  • await用来等待耗时操作的返回结果,这个操作会阻塞到后面的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import 'package:flutter/material.dart';

class LoginPage extends StatelessWidget{

@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text('登陆')
),
body: RaisedButton(
onPressed:() async{
// 接收一个返回结果
var result = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context){
return MenuPage();
},
// 设置路由名称 参数,这种跳转是否为首页,首页没有返回按钮
settings: RouteSettings(
name: 'menu',
arguments: '' // 参数
),
// 覆盖后的路由释放资源
maintainState: false,
// 是否为全屏
fullscreenDialog: true
));
print(result);
},
child: Text('登陆')
)
);
}
}

class MenuPage extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('菜单')
),
body: RaisedButton(
onPressed:(){
// 添加返回的数据
Navigator.of(context).pop({
"username":"admin"
});
},
child:Text("返回")
),
);
}
}

SnackBar

  • SnackBar是用户操作后,显示提示信息的一个控件,用于显示跳转携带返回的数据,类似Tost,会自动隐藏。SnackBar是以ScaffoldshowSnackBar方法来进行显示的
1
Scaffold.of(context).showSnackBar(SnackBar(content:Text('$result')));
  • 返回数据其实是特别容易的,只要在返回时带第二个参数就可以了
1
Navigator.pop(context,'华为手机');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import 'package:flutter/material.dart';

void main() {
runApp(MaterialApp(
title: '页面跳转返回数据',
home: FirstPage()
));
}

class FirstPage extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('商品页面')),
body:Center(
child:RouteButton(),
)
);
}
}

// 跳转的RouteButton
class RouteButton extends StatelessWidget {

@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: (){
_navigateToProduct(context);
},
child: Text('获取手机信息'),
);
}

// 内部方法 异步
_navigateToProduct(BuildContext context) async{

final result = await Navigator.push(
context,
MaterialPageRoute(builder:(context)=> Product())
);

Scaffold.of(context).showSnackBar(SnackBar(content:Text('$result')));
}
}

class Product extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text('手机页面')),
body:Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text('华为手机'),
onPressed: (){
// 多加一个参数就是返回的内容
Navigator.pop(context,'华为手机');
},
),
RaisedButton(
child: Text('小米手机'),
onPressed: (){
// 多加一个参数就是返回的内容
Navigator.pop(context,'小米手机');
},
),
],
)
)
);
}
}

命名路由

路由配置

  • 命名路由是区别于基本路由的一种存在,方便于大型项目中路由的统一管理,现在,在前面基本路由的项目基础上实现实现命名路由
  • 命名路由在使用前,需要在根组件main.dart中进行简单的配置(前面是页面路径,后面是页面中的组件名称)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_demo/LoginWidget.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 去掉右上角debug
debugShowCheckedModeBanner: false,
// 多个路由
routes: {
"/home" : (context) => LoginPage(),
"/menu": (context) => MenuPage(),
},
//initialRoute: "menu", // 指定哪一个当初始化的首页
);
}
}
  • 路由配置完成以后,在需要进行路由跳转的地方直接输入上面配置的名称使用就可以了
1
2
3
4
5
Navigator.of(context).pushNamed("/menu")
// 传递和获取参数
Navigator.of(context)
.pushNamed("/menu",arguments: "参数")
.then((value) => print(value));

路由拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_demo/LoginWidget.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 去掉右上角debug
debugShowCheckedModeBanner: false,
// 多个路由
routes: {
"home": (context) => LoginPage(),
//"menu": (context) => MenuPage(),
},
// 路由监听
onGenerateRoute: (RouteSettings s) {
// 没有在上面注册的路由 跳转就会报错
print(s.name); // 路由名称
// 根据路由名称进行业务逻辑设计
switch (s.name) {
case "meun":
return MaterialPageRoute(
builder: (context) {
return MenuPage();
},
settings: s);
break;
default:
}
},
);
}
}

路由封装

  • lib下创建routes/routes.dart文件,创建一个views文件夹存放视图
1
2
3
4
5
import 'package:flutter_demo/view/home.dart';

Map<String, WidgetBuilder> routes = {
"/home": {BuildContext context} => HomeView(),
}
  • 然后在main.dart中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'package:flutter_demo/routes/routes.dart';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// 使用路由
routes: routes,
home: Scaffold(appBar: AppBar(title: Text('接口测试')), body: DioDemo()),
);
}
}