基础工程:
Flutter系列(十一)实现商城首页和商品详情页_摸金青年v的博客-CSDN博客
Flutter系列(四)底部导航+顶部导航+图文列表完整代码_摸金青年v的博客-CSDN博客
一、前言
本文用flutter实现购物车和提交订单页,效果如下图:
二、使用组件
1. Card 卡片组件,扁平化风格
2. CheckBox 复选框组件,实现全选的功能
3. 数量加减插件 NumberControllerWidget
参考:flutter 自定义TextField,加减数量输入框 - 简书 (jianshu.com)
调整了下icon(加号和减号)的大小
三、完整代码
3.1 购物车页 cart.dart
import 'package:flutter/material.dart';
import 'package:flutter_play/NumberControllerWidget.dart';
import 'package:flutter_play/animationUtile.dart';
import 'package:flutter_play/checkout.dart';
/*购物车页*/
class CartPage extends StatefulWidget {
@override
State<CartPage> createState() => _CartPage();
}
class _CartPage extends State<CartPage> {
List<bool> isChecks = [false, false, false]; //复选框状态,默认未选中
bool isAllSelect = false; //全选状态
List listData = [
{
"store": "Apple苹果旗舰店",
"skuName": "Apple iPhone 14 Pro (A2892) 256GB 暗紫色 支持移动联通电信5G 双卡双待手机",
"price": "5988",
"image": "https://img-blog.csdnimg.cn/c6dfd375abf1433fa3a42951cc186a2b.jpeg",
},
{
"store": "小米旗舰店",
"skuName": "Redmi K60 骁龙8+处理器 2K高光屏 6400万超清相机 5500mAh长续航",
"price": "2588",
"image": "https://img-blog.csdnimg.cn/678c0686dc694b65ad6b20693dbc35f1.jpeg",
},
{
"store": "耐克品牌店",
"skuName": "夏季新款潮流鞋",
"price": "1299",
"image": "https://img-blog.csdnimg.cn/63efe7acbac74e7ebce85e3801f948e3.jpeg",
},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('购物车', style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16)),
foregroundColor: Colors.black, //字体颜色
backgroundColor: const Color(0xFFFBFBFB), //顶部背景色
),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
skuList(),//商品列表
],
),
)
),
bottomFix() //底部固定栏
],
),
);
}
//商品列表
Container skuList(){
return Container(
width: 500,
height: 800,
padding: const EdgeInsets.only(top: 5),
child: ListView.builder(
itemCount: listData.length, //商品个数
itemBuilder: (context, index) {
return Container(
height: 200,
width: 500,
padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), //内边距
margin: const EdgeInsets.fromLTRB(8, 5, 8, 0), //外边距
child: Card(
clipBehavior: Clip.hardEdge,
elevation: 2, //卡片海拔高度设置,立体感
child: InkWell(
splashColor: Colors.blue.withAlpha(30), //点击卡片,有蓝色透明度响应,扁平化
onTap: () {
}, //卡片点击
child: Column(
children: [
Row(
children: [
Checkbox(
value: isChecks[index],
onChanged:(value){
setState(() {
isChecks[index] = value!;
});
} ,
),//复选框
Text(listData[index]["store"], style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), //店铺
],
),
Row(
children: [
Checkbox(
value: isChecks[index],
onChanged:(value){
setState(() {
isChecks[index] = value!;
});
} ,
),
Image.network(listData[index]["image"], width: 90, height: 90, fit: BoxFit.cover),//商品图片
Container(
width: 200,
height: 100,
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start, //水平左对齐
children: [
Text(listData[index]["skuName"], style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), maxLines: 1, overflow: TextOverflow.ellipsis),
Container(
height: 20,
width: 150,
padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
decoration: BoxDecoration(
color: const Color(0xFFF2F2F2),
borderRadius: BorderRadius.circular(4), // 设置圆角
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('蓝色,64G,WLAN版', style: TextStyle(fontSize: 12, color: Colors.grey, fontWeight: FontWeight.w900)),
Icon(Icons.arrow_forward_ios, size: 12, color: Colors.grey)
],
)
), //选品
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Text('¥', style: TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
Text(listData[index]["price"], style: const TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
],
),
NumberControllerWidget(
addValueChanged: (num){print(num);},
removeValueChanged: (num){print(num);},
updateValueChanged: (num){},
)//选件组件
],
)
],
)
)
],
),
],
),
),
),
);
},
),
);
}
/*底部固定:去结算*/
Container bottomFix(){
return Container(
width: 500,
height: 50,
color: Colors.white,
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Checkbox(
value: isAllSelect,
onChanged:(value){
setState(() {
isAllSelect = value!;
//全选页面所有复选框
for (var i = 0; i < isChecks.length; i++) {
isChecks[i] = isAllSelect;
}
});
} ,
),//复选框
const Text(' 全选', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//
],
),
const Row(
children: [
Text('合计金额: ', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
Text('¥', style: TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
Text('7988', style: TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
],
),
TextButton (
style: ButtonStyle(
minimumSize: MaterialStateProperty.all(const Size(80, 30)),
backgroundColor: MaterialStateProperty.all(Colors.blueAccent),
foregroundColor: MaterialStateProperty.all<Color>(Colors.white), //字体颜色
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))) //圆角
),
child: const Text('去结算'),
onPressed: () {
Navigator.of(context).push(showPageFromRight(CheckOutPage()));
},
),
]
),
);
}
}
3.2 提交订单页 checkout.dart
import 'package:flutter/material.dart';
import 'package:flutter_play/NumberControllerWidget.dart';
/*结算页*/
class CheckOutPage extends StatefulWidget {
@override
State<CheckOutPage> createState() => _CheckOutPage();
}
class _CheckOutPage extends State<CheckOutPage> {
List listData = [
{
"store": "Apple苹果旗舰店",
"skuName": "Apple iPhone 14 Pro (A2892) 256GB 暗紫色 支持移动联通电信5G 双卡双待手机",
"price": "¥ 5988",
"image": "https://img-blog.csdnimg.cn/c6dfd375abf1433fa3a42951cc186a2b.jpeg",
},
{
"store": "小米旗舰店",
"skuName": "Redmi K60 骁龙8+处理器 2K高光屏 6400万超清相机 5500mAh长续航",
"price": "¥ 2588",
"image": "https://img-blog.csdnimg.cn/678c0686dc694b65ad6b20693dbc35f1.jpeg",
},
];
var userInfo = {
"nickName": "吴邪",
"phone": "139343254540",
"address": "北京市 海淀区 天秀路",
};
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
foregroundColor: Colors.black, //字体颜色
backgroundColor: const Color(0xFFFBFBFB), //顶部背景色
title: const Text('提交订单', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16)),
),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
addressInfo(), //地址选择
skuInfo(), //商品
priceInfo(), //金额优惠
],
),
),
),
bottomFix() //固定页面底部
],
)
);
}
Container addressInfo(){
return Container(
height: 90,
width: 500,
margin: const EdgeInsets.fromLTRB(8, 5, 8, 5),
child: Card(
clipBehavior: Clip.hardEdge,
elevation: 2,
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
//弹出地址管理弹窗
}, //卡片点击
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start, //水平左对齐
children: [
Text(userInfo["address"]!, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
Row(
children: [
Text(userInfo["nickName"]!, style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 14)),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
child: Text(userInfo["phone"]!, style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 14)),
),
],
)
],
),
const Icon(Icons.arrow_forward_ios, size: 12)
],
),
)
),
)
);
}
//商品列表
SizedBox skuInfo(){
return SizedBox(
width: 500,
height: 370,
child: ListView.builder(
itemCount: listData.length,
itemBuilder: (context, index) {
return Container(
height: 180,
width: 500,
margin: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Card(
clipBehavior: Clip.hardEdge,
elevation: 2,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
children: [
Text(listData[index]["store"], style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
],
),
Row(
children: [
Image.network(listData[index]["image"], width: 90, height: 90, fit: BoxFit.cover),//商品图片
Container(
width: 240,
height: 90,
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, //水平左对齐
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start, //水平左对齐
children: [
Text(listData[index]["skuName"], style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), maxLines: 1, overflow: TextOverflow.ellipsis),
Container(
height: 20,
width: 150,
padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
decoration: BoxDecoration(
color: const Color(0xFFF2F2F2),
borderRadius: BorderRadius.circular(4), // 设置圆角
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('蓝色,64G,WLAN版', style: TextStyle(fontSize: 12, color: Colors.grey, fontWeight: FontWeight.w900)),
],
)
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(listData[index]["price"], style: const TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
NumberControllerWidget(
addValueChanged: (num){print(num);},
removeValueChanged: (num){print(num);},
updateValueChanged: (num){},
)//选件组件
],
)
],
)
)
],
),
],
),
)
),
);
},
),
);
}
Container priceInfo(){
return Container(
height: 180,
width: 500,
margin: const EdgeInsets.fromLTRB(8, 5, 8, 5),
child: const Card(
clipBehavior: Clip.hardEdge,
elevation: 2,
child: Padding(
padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('商品金额', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
Text('¥5988', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('运费', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
Text('¥8', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('优惠券', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
Text('-¥20', style: TextStyle(color: Colors.redAccent, fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('积分', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
Text('-¥3', style: TextStyle(color: Colors.redAccent, fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
],
)
],
),
)
)
);
}
/*底部固定:提交订单*/
Container bottomFix(){
return Container(
width: 500,
height: 50,
color: Colors.white,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Row(
children: [
Text('应支付: ', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//
Text('¥5968', style: TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//商品名称
],
),
TextButton (
style: ButtonStyle(
minimumSize: MaterialStateProperty.all(const Size(90, 30)),
backgroundColor: MaterialStateProperty.all(Colors.blueAccent),
foregroundColor: MaterialStateProperty.all<Color>(Colors.white), //字体颜色
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))) //圆角
),
child: const Text('提交订单'),
onPressed: () {
//跳转收银台
},
),
]
),
)
);
}
}
四、解决问题
4.1. 全选逻辑实现
1)如果点击非全选框,两种状态:未选择和选中,其他复选框之间是否选中不能互相影响,所以会有取值数组,保存每个复选框的状态
List<bool> isChecks = [false, false, false]; //其他复选框的状态,默认未选中
Checkbox(
value: isChecks[index],
onChanged:(value){
setState(() {
isChecks[index] = value!; //点击则逻辑取反,false和true直接来回转换
});
} ,
),//复选框
2)如果点击全选框,需要更新页面其他复选框的状态和全选框保持一致,需要循环处理,
bool isAllSelect = false; //全选框的状态
Checkbox(
value: isAllSelect,
onChanged:(value){
setState(() {
isAllSelect = value!; //全选框逻辑取反
for (var i = 0; i < isChecks.length; i++) {
isChecks[i] = isAllSelect; //页面所有复选框, 状态和全选框保持一致
}
});
} ,
),//复选框
本文结束