• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Flutter系列十二实现购物车和提交订单页

武飞扬头像
摸金青年v
帮助1

基础工程:

一、前言

        本文用flutter实现购物车提交订单页,效果如下图:

学新通               学新通

二、使用组件

1. Card 卡片组件,扁平化风格

2. CheckBox 复选框组件,实现全选的功能

3. 数量加减插件    NumberControllerWidget

参考:flutter 自定义TextField,加减数量输入框 - 简书 (jianshu.com)

调整了下icon(加号和减号)的大小

三、完整代码

3.1 购物车页  cart.dart

  1.  
    import 'package:flutter/material.dart';
  2.  
    import 'package:flutter_play/NumberControllerWidget.dart';
  3.  
    import 'package:flutter_play/animationUtile.dart';
  4.  
    import 'package:flutter_play/checkout.dart';
  5.  
     
  6.  
    /*购物车页*/
  7.  
    class CartPage extends StatefulWidget {
  8.  
    @override
  9.  
    State<CartPage> createState() => _CartPage();
  10.  
    }
  11.  
     
  12.  
    class _CartPage extends State<CartPage> {
  13.  
     
  14.  
    List<bool> isChecks = [false, false, false]; //复选框状态,默认未选中
  15.  
    bool isAllSelect = false; //全选状态
  16.  
    List listData = [
  17.  
    {
  18.  
    "store": "Apple苹果旗舰店",
  19.  
    "skuName": "Apple iPhone 14 Pro (A2892) 256GB 暗紫色 支持移动联通电信5G 双卡双待手机",
  20.  
    "price": "5988",
  21.  
    "image": "https://img-blog.csdnimg.cn/c6dfd375abf1433fa3a42951cc186a2b.jpeg",
  22.  
    },
  23.  
    {
  24.  
    "store": "小米旗舰店",
  25.  
    "skuName": "Redmi K60 骁龙8 处理器 2K高光屏 6400万超清相机 5500mAh长续航",
  26.  
    "price": "2588",
  27.  
    "image": "https://img-blog.csdnimg.cn/678c0686dc694b65ad6b20693dbc35f1.jpeg",
  28.  
    },
  29.  
    {
  30.  
    "store": "耐克品牌店",
  31.  
    "skuName": "夏季新款潮流鞋",
  32.  
    "price": "1299",
  33.  
    "image": "https://img-blog.csdnimg.cn/63efe7acbac74e7ebce85e3801f948e3.jpeg",
  34.  
    },
  35.  
    ];
  36.  
     
  37.  
    @override
  38.  
    Widget build(BuildContext context) {
  39.  
    return Scaffold(
  40.  
    appBar: AppBar(
  41.  
    title: const Text('购物车', style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16)),
  42.  
    foregroundColor: Colors.black, //字体颜色
  43.  
    backgroundColor: const Color(0xFFFBFBFB), //顶部背景色
  44.  
    ),
  45.  
    body: Column(
  46.  
    children: [
  47.  
    Expanded(
  48.  
    child: SingleChildScrollView(
  49.  
    child: Column(
  50.  
    children: [
  51.  
    skuList(),//商品列表
  52.  
    ],
  53.  
    ),
  54.  
    )
  55.  
    ),
  56.  
    bottomFix() //底部固定栏
  57.  
    ],
  58.  
    ),
  59.  
    );
  60.  
    }
  61.  
     
  62.  
    //商品列表
  63.  
    Container skuList(){
  64.  
    return Container(
  65.  
    width: 500,
  66.  
    height: 800,
  67.  
    padding: const EdgeInsets.only(top: 5),
  68.  
    child: ListView.builder(
  69.  
    itemCount: listData.length, //商品个数
  70.  
    itemBuilder: (context, index) {
  71.  
    return Container(
  72.  
    height: 200,
  73.  
    width: 500,
  74.  
    padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), //内边距
  75.  
    margin: const EdgeInsets.fromLTRB(8, 5, 8, 0), //外边距
  76.  
    child: Card(
  77.  
    clipBehavior: Clip.hardEdge,
  78.  
    elevation: 2, //卡片海拔高度设置,立体感
  79.  
    child: InkWell(
  80.  
    splashColor: Colors.blue.withAlpha(30), //点击卡片,有蓝色透明度响应,扁平化
  81.  
    onTap: () {
  82.  
     
  83.  
    }, //卡片点击
  84.  
    child: Column(
  85.  
    children: [
  86.  
    Row(
  87.  
    children: [
  88.  
    Checkbox(
  89.  
    value: isChecks[index],
  90.  
    onChanged:(value){
  91.  
    setState(() {
  92.  
    isChecks[index] = value!;
  93.  
    });
  94.  
    } ,
  95.  
    ),//复选框
  96.  
    Text(listData[index]["store"], style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), //店铺
  97.  
    ],
  98.  
    ),
  99.  
    Row(
  100.  
    children: [
  101.  
    Checkbox(
  102.  
    value: isChecks[index],
  103.  
    onChanged:(value){
  104.  
    setState(() {
  105.  
    isChecks[index] = value!;
  106.  
    });
  107.  
    } ,
  108.  
    ),
  109.  
    Image.network(listData[index]["image"], width: 90, height: 90, fit: BoxFit.cover),//商品图片
  110.  
    Container(
  111.  
    width: 200,
  112.  
    height: 100,
  113.  
    padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
  114.  
    child: Column(
  115.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  116.  
    children: [
  117.  
    Column(
  118.  
    crossAxisAlignment: CrossAxisAlignment.start, //水平左对齐
  119.  
    children: [
  120.  
    Text(listData[index]["skuName"], style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), maxLines: 1, overflow: TextOverflow.ellipsis),
  121.  
    Container(
  122.  
    height: 20,
  123.  
    width: 150,
  124.  
    padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
  125.  
    decoration: BoxDecoration(
  126.  
    color: const Color(0xFFF2F2F2),
  127.  
    borderRadius: BorderRadius.circular(4), // 设置圆角
  128.  
    ),
  129.  
    child: const Row(
  130.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  131.  
    children: [
  132.  
    Text('蓝色,64G,WLAN版', style: TextStyle(fontSize: 12, color: Colors.grey, fontWeight: FontWeight.w900)),
  133.  
    Icon(Icons.arrow_forward_ios, size: 12, color: Colors.grey)
  134.  
    ],
  135.  
    )
  136.  
    ), //选品
  137.  
    ],
  138.  
    ),
  139.  
    Row(
  140.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  141.  
    children: [
  142.  
    Row(
  143.  
    children: [
  144.  
    const Text('¥', style: TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
  145.  
    Text(listData[index]["price"], style: const TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
  146.  
    ],
  147.  
    ),
  148.  
    NumberControllerWidget(
  149.  
    addValueChanged: (num){print(num);},
  150.  
    removeValueChanged: (num){print(num);},
  151.  
    updateValueChanged: (num){},
  152.  
    )//选件组件
  153.  
    ],
  154.  
    )
  155.  
    ],
  156.  
    )
  157.  
    )
  158.  
    ],
  159.  
    ),
  160.  
    ],
  161.  
    ),
  162.  
    ),
  163.  
    ),
  164.  
    );
  165.  
    },
  166.  
    ),
  167.  
    );
  168.  
    }
  169.  
     
  170.  
    /*底部固定:去结算*/
  171.  
    Container bottomFix(){
  172.  
    return Container(
  173.  
    width: 500,
  174.  
    height: 50,
  175.  
    color: Colors.white,
  176.  
    padding: const EdgeInsets.all(8),
  177.  
    child: Row(
  178.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  179.  
    children: [
  180.  
    Row(
  181.  
    children: [
  182.  
    Checkbox(
  183.  
    value: isAllSelect,
  184.  
    onChanged:(value){
  185.  
    setState(() {
  186.  
    isAllSelect = value!;
  187.  
    //全选页面所有复选框
  188.  
    for (var i = 0; i < isChecks.length; i ) {
  189.  
    isChecks[i] = isAllSelect;
  190.  
    }
  191.  
    });
  192.  
    } ,
  193.  
    ),//复选框
  194.  
    const Text(' 全选', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//
  195.  
    ],
  196.  
    ),
  197.  
    const Row(
  198.  
    children: [
  199.  
    Text('合计金额: ', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
  200.  
    Text('¥', style: TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
  201.  
    Text('7988', style: TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
  202.  
    ],
  203.  
    ),
  204.  
    TextButton (
  205.  
    style: ButtonStyle(
  206.  
    minimumSize: MaterialStateProperty.all(const Size(80, 30)),
  207.  
    backgroundColor: MaterialStateProperty.all(Colors.blueAccent),
  208.  
    foregroundColor: MaterialStateProperty.all<Color>(Colors.white), //字体颜色
  209.  
    shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))) //圆角
  210.  
    ),
  211.  
    child: const Text('去结算'),
  212.  
    onPressed: () {
  213.  
    Navigator.of(context).push(showPageFromRight(CheckOutPage()));
  214.  
    },
  215.  
    ),
  216.  
    ]
  217.  
    ),
  218.  
    );
  219.  
    }
  220.  
     
  221.  
    }
学新通

3.2 提交订单页  checkout.dart

  1.  
    import 'package:flutter/material.dart';
  2.  
    import 'package:flutter_play/NumberControllerWidget.dart';
  3.  
     
  4.  
    /*结算页*/
  5.  
    class CheckOutPage extends StatefulWidget {
  6.  
    @override
  7.  
    State<CheckOutPage> createState() => _CheckOutPage();
  8.  
    }
  9.  
     
  10.  
    class _CheckOutPage extends State<CheckOutPage> {
  11.  
     
  12.  
    List listData = [
  13.  
    {
  14.  
    "store": "Apple苹果旗舰店",
  15.  
    "skuName": "Apple iPhone 14 Pro (A2892) 256GB 暗紫色 支持移动联通电信5G 双卡双待手机",
  16.  
    "price": "¥ 5988",
  17.  
    "image": "https://img-blog.csdnimg.cn/c6dfd375abf1433fa3a42951cc186a2b.jpeg",
  18.  
    },
  19.  
    {
  20.  
    "store": "小米旗舰店",
  21.  
    "skuName": "Redmi K60 骁龙8 处理器 2K高光屏 6400万超清相机 5500mAh长续航",
  22.  
    "price": "¥ 2588",
  23.  
    "image": "https://img-blog.csdnimg.cn/678c0686dc694b65ad6b20693dbc35f1.jpeg",
  24.  
    },
  25.  
    ];
  26.  
     
  27.  
    var userInfo = {
  28.  
    "nickName": "吴邪",
  29.  
    "phone": "139343254540",
  30.  
    "address": "北京市 海淀区 天秀路",
  31.  
    };
  32.  
     
  33.  
    @override
  34.  
    Widget build(BuildContext context) {
  35.  
    return Scaffold(
  36.  
    appBar: AppBar(
  37.  
    foregroundColor: Colors.black, //字体颜色
  38.  
    backgroundColor: const Color(0xFFFBFBFB), //顶部背景色
  39.  
    title: const Text('提交订单', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16)),
  40.  
    ),
  41.  
    body: Column(
  42.  
    children: [
  43.  
    Expanded(
  44.  
    child: SingleChildScrollView(
  45.  
    child: Column(
  46.  
    children: [
  47.  
    addressInfo(), //地址选择
  48.  
    skuInfo(), //商品
  49.  
    priceInfo(), //金额优惠
  50.  
    ],
  51.  
    ),
  52.  
    ),
  53.  
    ),
  54.  
    bottomFix() //固定页面底部
  55.  
    ],
  56.  
    )
  57.  
    );
  58.  
    }
  59.  
     
  60.  
    Container addressInfo(){
  61.  
    return Container(
  62.  
    height: 90,
  63.  
    width: 500,
  64.  
    margin: const EdgeInsets.fromLTRB(8, 5, 8, 5),
  65.  
    child: Card(
  66.  
    clipBehavior: Clip.hardEdge,
  67.  
    elevation: 2,
  68.  
    child: InkWell(
  69.  
    splashColor: Colors.blue.withAlpha(30),
  70.  
    onTap: () {
  71.  
    //弹出地址管理弹窗
  72.  
    }, //卡片点击
  73.  
    child: Padding(
  74.  
    padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
  75.  
    child: Row(
  76.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  77.  
    children: [
  78.  
    Column(
  79.  
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  80.  
    crossAxisAlignment: CrossAxisAlignment.start, //水平左对齐
  81.  
    children: [
  82.  
    Text(userInfo["address"]!, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
  83.  
    Row(
  84.  
    children: [
  85.  
    Text(userInfo["nickName"]!, style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 14)),
  86.  
    Padding(
  87.  
    padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
  88.  
    child: Text(userInfo["phone"]!, style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 14)),
  89.  
    ),
  90.  
    ],
  91.  
    )
  92.  
    ],
  93.  
    ),
  94.  
    const Icon(Icons.arrow_forward_ios, size: 12)
  95.  
    ],
  96.  
    ),
  97.  
    )
  98.  
    ),
  99.  
    )
  100.  
    );
  101.  
    }
  102.  
     
  103.  
    //商品列表
  104.  
    SizedBox skuInfo(){
  105.  
    return SizedBox(
  106.  
    width: 500,
  107.  
    height: 370,
  108.  
    child: ListView.builder(
  109.  
    itemCount: listData.length,
  110.  
    itemBuilder: (context, index) {
  111.  
    return Container(
  112.  
    height: 180,
  113.  
    width: 500,
  114.  
    margin: const EdgeInsets.fromLTRB(10, 5, 10, 5),
  115.  
    child: Card(
  116.  
    clipBehavior: Clip.hardEdge,
  117.  
    elevation: 2,
  118.  
    child: Padding(
  119.  
    padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
  120.  
    child: Column(
  121.  
    mainAxisAlignment: MainAxisAlignment.spaceAround,
  122.  
    children: [
  123.  
    Row(
  124.  
    children: [
  125.  
    Text(listData[index]["store"], style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
  126.  
    ],
  127.  
    ),
  128.  
    Row(
  129.  
    children: [
  130.  
    Image.network(listData[index]["image"], width: 90, height: 90, fit: BoxFit.cover),//商品图片
  131.  
    Container(
  132.  
    width: 240,
  133.  
    height: 90,
  134.  
    padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
  135.  
    child: Column(
  136.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  137.  
    crossAxisAlignment: CrossAxisAlignment.start, //水平左对齐
  138.  
    children: [
  139.  
    Column(
  140.  
    crossAxisAlignment: CrossAxisAlignment.start, //水平左对齐
  141.  
    children: [
  142.  
    Text(listData[index]["skuName"], style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), maxLines: 1, overflow: TextOverflow.ellipsis),
  143.  
    Container(
  144.  
    height: 20,
  145.  
    width: 150,
  146.  
    padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
  147.  
    decoration: BoxDecoration(
  148.  
    color: const Color(0xFFF2F2F2),
  149.  
    borderRadius: BorderRadius.circular(4), // 设置圆角
  150.  
    ),
  151.  
    child: const Row(
  152.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  153.  
    children: [
  154.  
    Text('蓝色,64G,WLAN版', style: TextStyle(fontSize: 12, color: Colors.grey, fontWeight: FontWeight.w900)),
  155.  
    ],
  156.  
    )
  157.  
    ),
  158.  
    ],
  159.  
    ),
  160.  
    Row(
  161.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  162.  
    children: [
  163.  
    Text(listData[index]["price"], style: const TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//金额
  164.  
    NumberControllerWidget(
  165.  
    addValueChanged: (num){print(num);},
  166.  
    removeValueChanged: (num){print(num);},
  167.  
    updateValueChanged: (num){},
  168.  
    )//选件组件
  169.  
    ],
  170.  
    )
  171.  
    ],
  172.  
    )
  173.  
    )
  174.  
    ],
  175.  
    ),
  176.  
    ],
  177.  
    ),
  178.  
    )
  179.  
     
  180.  
    ),
  181.  
    );
  182.  
    },
  183.  
    ),
  184.  
    );
  185.  
    }
  186.  
     
  187.  
    Container priceInfo(){
  188.  
    return Container(
  189.  
    height: 180,
  190.  
    width: 500,
  191.  
    margin: const EdgeInsets.fromLTRB(8, 5, 8, 5),
  192.  
    child: const Card(
  193.  
    clipBehavior: Clip.hardEdge,
  194.  
    elevation: 2,
  195.  
    child: Padding(
  196.  
    padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
  197.  
    child: Column(
  198.  
    mainAxisAlignment: MainAxisAlignment.spaceAround,
  199.  
    children: [
  200.  
    Row(
  201.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  202.  
    children: [
  203.  
    Text('商品金额', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
  204.  
    Text('¥5988', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
  205.  
    ],
  206.  
    ),
  207.  
    Row(
  208.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  209.  
    children: [
  210.  
    Text('运费', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
  211.  
    Text('¥8', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
  212.  
    ],
  213.  
    ),
  214.  
    Row(
  215.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  216.  
    children: [
  217.  
    Text('优惠券', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
  218.  
    Text('-¥20', style: TextStyle(color: Colors.redAccent, fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
  219.  
    ],
  220.  
    ),
  221.  
    Row(
  222.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  223.  
    children: [
  224.  
    Text('积分', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
  225.  
    Text('-¥3', style: TextStyle(color: Colors.redAccent, fontSize: 14, fontWeight: FontWeight.w400)),//商品名称
  226.  
    ],
  227.  
    )
  228.  
    ],
  229.  
    ),
  230.  
    )
  231.  
    )
  232.  
    );
  233.  
    }
  234.  
     
  235.  
    /*底部固定:提交订单*/
  236.  
    Container bottomFix(){
  237.  
    return Container(
  238.  
    width: 500,
  239.  
    height: 50,
  240.  
    color: Colors.white,
  241.  
    child: Padding(
  242.  
    padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
  243.  
    child: Row(
  244.  
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
  245.  
    children: [
  246.  
    const Row(
  247.  
    children: [
  248.  
    Text('应支付: ', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),//
  249.  
    Text('¥5968', style: TextStyle(color: Colors.redAccent, fontSize: 16, fontWeight: FontWeight.w600)),//商品名称
  250.  
    ],
  251.  
    ),
  252.  
    TextButton (
  253.  
    style: ButtonStyle(
  254.  
    minimumSize: MaterialStateProperty.all(const Size(90, 30)),
  255.  
    backgroundColor: MaterialStateProperty.all(Colors.blueAccent),
  256.  
    foregroundColor: MaterialStateProperty.all<Color>(Colors.white), //字体颜色
  257.  
    shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))) //圆角
  258.  
    ),
  259.  
    child: const Text('提交订单'),
  260.  
    onPressed: () {
  261.  
    //跳转收银台
  262.  
    },
  263.  
    ),
  264.  
    ]
  265.  
    ),
  266.  
    )
  267.  
    );
  268.  
    }
  269.  
     
  270.  
    }
学新通

 四、解决问题

4.1. 全选逻辑实现

1)如果点击非全选框,两种状态:未选择和选中,其他复选框之间是否选中不能互相影响,所以会有取值数组,保存每个复选框的状态

  1.  
    List<bool> isChecks = [false, false, false]; //其他复选框的状态,默认未选中
  2.  
     
  3.  
    Checkbox(
  4.  
    value: isChecks[index],
  5.  
    onChanged:(value){
  6.  
    setState(() {
  7.  
    isChecks[index] = value!; //点击则逻辑取反,false和true直接来回转换
  8.  
    });
  9.  
    } ,
  10.  
    ),//复选框

2)如果点击全选框,需要更新页面其他复选框的状态和全选框保持一致,需要循环处理,

  1.  
    bool isAllSelect = false; //全选框的状态
  2.  
     
  3.  
    Checkbox(
  4.  
    value: isAllSelect,
  5.  
    onChanged:(value){
  6.  
    setState(() {
  7.  
    isAllSelect = value!; //全选框逻辑取反
  8.  
    for (var i = 0; i < isChecks.length; i ) {
  9.  
    isChecks[i] = isAllSelect; //页面所有复选框, 状态和全选框保持一致
  10.  
    }
  11.  
    });
  12.  
    } ,
  13.  
    ),//复选框

本文结束

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhhbag
系列文章
更多 icon
同类精品
更多 icon
继续加载