Flutter-购物小测试

在学习 Provider 的时候,我在 flutter 的官方小教程中,看到了一个 demo,有关购买商品,加入购物车的。刚学习 Provider 的时候,很多地方不明白,所以那个案例也没有能够理解。

今天的我已经能使用 Navigator 和 Provider 的知识了。利用我已有的知识,可以大概模仿出官方 demo 的样子了。

路由分析

这个 demo 涉及到三个页面,第一个是登录界面,第二个是商品界面,第三个是购物车界面。

分别命名为:Login, Catalog, Cart

1
2
3
4
5
6
7
8
9
import 'package:dayly/Login.dart';
import 'package:dayly/Catalog.dart';
import 'package:dayly/Cart.dart';

var routes={
'login':(context) => const Login(),
"catalog":(context) => const Catalog(),
"cart":(context) => const Cart(),
};

共享数据

建立数据模型

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
class GoodsModel extends ChangeNotifier{//共享数据的数据模型
final List<String> _itemNames = [//模拟数据,商品名称
'Code Smell',
'Control Flow',
'Interpreter',
'Recursion',
'Sprint',
'Heisenbug',
'Spaghetti',
'Hydra Code',
'Off-By-One',
'Scope',
'Callback',
'Closure',
'Automata',
'Bit Shift',
'Currying',
];
final List<bool> _isSelected=List.generate(15, (index) => false);//都设置为未加入购物车
final List<String> _selectedItems=[];//已选中的商品名称

List get itemNames=>_itemNames;//暴露以上私有成员变量
List get selectedItems=>_selectedItems;
List get isSelected=>_isSelected;

void selectItem(int id){//加入购物车
_selectedItems.add(_itemNames[id]);
_isSelected[id]=true;
notifyListeners();
}

void unselectItem(int id){//移出购物车
_selectedItems.remove(_itemNames[id]);
_isSelected[id]=false;
notifyListeners();
}

}

将数据模型放置在高级节点上,使其子孙组件都能访问共享数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(//使用共享数据
create: (context)=>GoodsModel(),
child: MaterialApp(
title: "Store",
debugShowCheckedModeBanner: false,
routes: routes,//设置路由
initialRoute: 'login',//默认路由,即app首页
),
);
}
}

Login

简单的登录界面实现。

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
import 'package:flutter/material.dart';

class Login extends StatelessWidget {
const Login({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(//标准结构
body: Center(//整体居中,当然了,只是它的子组件居中,水平和垂直
child: Container(//有子组件则匹配子组件的大小,没有子组件则看父级有无传递约束,有约束则都尽可能大,如果这个约束是无限,那就尽可能小。自己设定的宽高会覆盖前面所说的大小。
padding: const EdgeInsets.all(50),//加个内边距
color: Colors.white70,
child: Column(//Column会占满可用空间
mainAxisAlignment: MainAxisAlignment.center,//想让登录信息的部分居中就要这样做设置,因为上面的Center只是让Column的父级居中了
children: [
const Text(
'Welcome',
style: TextStyle(
color: Colors.black,
fontSize: 30,
fontWeight: FontWeight.bold,//标题加粗
),
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Username',//设置占位信息
),
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Password',
),
obscureText: true,//模糊文本,也就是隐藏文本信息,密码框嘛
),
const SizedBox(//占位的空白组件
height: 24,
),
ElevatedButton(//普通的按钮
onPressed: () {
Navigator.pushNamed(context, 'catalog');//登录跳转
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.black)//设置背景色
),
child: const Text('ENTER'),
)
],
),
),
),

);
}
}

登录界面

Catalog

商品目录。

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import 'package:dayly/main.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Catalog extends StatelessWidget {
const Catalog({Key? key}) : super(key: key);


@override
Widget build(BuildContext context) {
final List<String> itemNames = context.read<GoodsModel>().itemNames as List<String>;//获取共享数据中的商品名称
//根据商品名称生成每一行的商品信息。包括了商品的图片(这里用随机颜色代替),商品id,商品名称。商品类是自定义的。
List<Good> goods=List.generate(itemNames.length, (index) => Good(color: Colors.primaries[index],id: index,name: itemNames[index],));

return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title: const Text("Catalog"),
centerTitle: true,
actions: [IconButton(
onPressed: (){
Navigator.pushNamed(context, 'cart');
},
icon: const Icon(Icons.shopping_cart)
)],
),
body: GridView(//这里之所以使用网格列表,是因为网格列表可以滚动,且设置垂直间距更方便
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,//一列
mainAxisSpacing: 0,//每一行之间的距离
mainAxisExtent: 80//每一行的高度
),
children: goods,//刚刚生成是商品们
),
);
}
}

class Good extends StatelessWidget {//自定义的商品组件
final int id;//商品id
final Color color;//商品图片(颜色替代)
final String name;//商品名称

const Good({Key? key, required this.color, required this.id, required this.name}) : super(key: key);

@override
Widget build(BuildContext context) {
bool selected=context.watch<GoodsModel>().isSelected[id];//确认商品是否选中再初始化
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16,vertical: 8),
child: Row(//每个商品就是一行
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [//一行中的所有列
Container(//颜色块
width: 50,
height: 50,
color: color,
),
const SizedBox(width: 24),//空白块分隔
Expanded(//拓展区,自动占满剩余未分配区域
child: Text(name,style: const TextStyle(
fontSize: 24
),),
),
TextButton(//添加购物车按钮
onPressed: (){
if(selected==true){//移出购物车
selected=!selected;
context.read<GoodsModel>().unselectItem(id);
}
else{//加入购物车
selected=!selected;
context.read<GoodsModel>().selectItem(id);
}
},
child: selected
?const Icon(Icons.check,size: 20,)//已经选中,则变成勾勾图标
:const Text(//未选中,则文字ADD
"ADD",
style: TextStyle(
color: Colors.black,
fontSize: 20
),
),
),
const SizedBox(width: 24),
],
),
);
}
}

商品界面

Cart

购物车。

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
77
78
79
80
81
import 'package:dayly/main.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Cart extends StatelessWidget {
const Cart({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title: const Text("Cart"),
centerTitle: true,
),
body: MyCart(),
);
}
}

class MyCart extends StatelessWidget {
const MyCart({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
List<String> texts=context.watch<GoodsModel>().selectedItems as List<String>;//获取共享数据中已经选择的商品
List<Text> goods=List.generate(texts.length, (index) => Text(//生成购物车清单
texts[index],
style: const TextStyle(
fontSize: 30
),
));
int price=texts.length*42;//每件商品按42美元计算
return Container(
color: Colors.orange,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20,vertical: 16),
child: Column(
children: [
Container(
height: 400,
child: Row(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: goods,
)
],
),
),
Container(
height: 150,
decoration: const BoxDecoration(
border: Border.symmetric(horizontal: BorderSide(
style: BorderStyle.solid
))
),
child: Row(
children: [
const SizedBox(width: 40,),
const Text("\$",style: TextStyle(fontSize: 50),),
Text("$price",style: const TextStyle(fontSize: 50),),
const SizedBox(width: 50,),
TextButton(
onPressed: (){},
style: ButtonStyle(
backgroundColor:MaterialStateProperty.all(Colors.white),
minimumSize: MaterialStateProperty.all(const Size(120, 50))
),
child: const Text("BUY"),
)
],
),
)
],
),
),
);
}
}

购物车界面

总结

  • 难能可贵的综合运用
  • 布局的思维模式和熟练度有待提升