Flower_gift 简单的Flutter实战app(六)

ed94bbb1f573dd5dcd00505fab7cfb72.png
项目git地址:flower_gift
这次的内容是购物车界面的各种逻辑完成。还是先简单看看效果:
2222.gif

先说明一下,对于Provide的运用,只要把购物车这一个页面的逻辑理清楚,用好这个数据管理,那么Provide的运用应该就没有什么大问题了。 其次是本来这个是有后台交互的购物车,这样没什么难度,我也就基本也按照技术胖的教学课程来重新做了一次购物车,收获蛮大的。 下面还是就分环节介绍吧

本地数据持久化方案-- shared_preferences

这个东西呢,有点像iOS中的NSUserDefaults,也是依靠键值对存储数据,可以存储一些像int,String,List等一些数据到本地,很轻量级,使用也比较方便,只需要简单学习一些就可以上手了

tabBar栏的简单的切换逻辑

在最初搭建页面框架的时候,我就已经创建了一个TabBarTapProvide来管理底部tab标签栏的切换,所以我们在商品详情页,要想直接跳转到购物车tab页面就很简单了
找到商品详情页的detailsBottom_widget

 InkWell(
           onTap: (){
          //改变tab的选中index来控制底部tab的切换
          Provide.value<TabBarTapProvide>(context).bottomTabBarTap(3);
          //pop出当前页面
          Navigator.pop(context);
        },

在购物车点击里面加上这个代码即可

整个购物车Provide文件
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../model/cartInfo_model_entity.dart';

//购物车简单逻辑:
/*
 * 因为我们这边购物车就不去和后台交互了,这样比较简单,我们就采用这个本地存储数据
 * 的方式来做。
 * 大概思路也就是在加入购物车的时候将商品的几个关键数据,先做成数据字典,然后加入到
 * 购物车List中,最后将这个List转化为String存储,取出的时候,先将String转为List来进行操作
 * 
 */

final cartInfoKey = "cartInfo";

class CartProvide with ChangeNotifier{
    String cartString = "[]";
    List<CartInfoModel> cartList = [];
    double allPrice = 0;//总价格
    int allGoodsCount = 0;//商品总数量
    bool isAllSelect = true;//是否全选
    
    //存储商品
    saveGoods(goodsId,goodsName,count,price,images) async{
       SharedPreferences prefs = await SharedPreferences.getInstance();
       //首先获取数据库中的数据(String 类型)
       cartString = prefs.getString(cartInfoKey);
       //类型转换
       var temp = cartString==null?[]:json.decode(cartString.toString());
       List<Map> tempList = (temp as List).cast();

       //加入购物车,判断购物车是否已有当前商品,有count++,没有就添加整个商品
       bool isExist = false;//是否存在
       int ival = 0;//临时索引
       allPrice = 0;
       allGoodsCount = 0;
       tempList.forEach((item){
           if(item["goodsId"] == goodsId){
             //已经存在
             tempList[ival]["count"] = item["count"]+1;
             cartList[ival].count++;//数据模型++
             isExist = true;
           }
           //判断商品否是选中状态
           if(item["isCheck"]){
             //商品数量和商品总价格计算
             allPrice += (cartList[ival].price * cartList[ival].count);
             allGoodsCount += cartList[ival].count;
           }

       });
       //如果商品不存在
       if(!isExist){
          Map<String,dynamic> newGoods = {
               "goodsId":goodsId,
               "goodsName":goodsName,
               "count":count,
               "price":price,
               "images":images,
               "isCheck":true//默认加入购物车是选中的
          };
          tempList.add(newGoods);
          cartList.add(
             CartInfoModel.fromJson(newGoods)
          );
          allPrice += count * price;
          allGoodsCount += count;

       }
       //持久化
       cartString = json.encode(tempList).toString();//List=>String
       print("购物车数据是:$cartString");
       prefs.setString(cartInfoKey, cartString);
       notifyListeners();
       

    }

    
    //清空购物车
    removeCart() async{
      SharedPreferences prefs = await SharedPreferences.getInstance();
      prefs.remove(cartInfoKey);
      print("购物车已经清空");
      notifyListeners();
    }

    //查询购物车
    getCartInfo() async {
       SharedPreferences prefs = await SharedPreferences.getInstance();
       cartString = prefs.getString(cartInfoKey);
       cartList = [];
       if(cartString == null){
          cartList = [];
       }else{
           List<Map> tempList = (json.decode(cartString.toString()) as List).cast();
           allPrice = 0;
           allGoodsCount = 0;
           isAllSelect = true;
           tempList.forEach((item){
                if(item["isCheck"]){
                   allPrice += (item["count"] * item["price"]);
                   allGoodsCount += item["count"];
                }else{
                   isAllSelect = false;
                }
                cartList.add(CartInfoModel.fromJson(item));
           });
       }
       notifyListeners();

    }


    //删除购物车商品
    deleteSingleGoods(String goodsId) async{
        SharedPreferences prefs = await SharedPreferences.getInstance();
        cartString = prefs.getString(cartInfoKey);
        List<Map> tempList = (json.decode(cartString.toString()) as List).cast();
        int tempIndex = 0;
        int delIndex = 0;
        //这个遍历只是获取这个要删除商品的索引,因为dart在循环中不能取修改List的内容
        tempList.forEach((item){
            if(item["goodsId"] == goodsId){
                delIndex = tempIndex;
            }
            tempIndex++;
        });
        tempList.removeAt(delIndex);
        cartString = json.encode(tempList).toString();
        prefs.setString(cartInfoKey, cartString);
        //重新获取购物车数据,刷新购物车界面
        await getCartInfo();

    }

   //改变单个商品的选中状态
   changeSelectState(CartInfoModel cartItem) async{
       SharedPreferences prefs = await SharedPreferences.getInstance();
       cartString = prefs.getString(cartInfoKey);
       List<Map> tempList = (json.decode(cartString.toString()) as List).cast();
       int tempIndex = 0;
       int changeIndex = 0;
       tempList.forEach((item){
              if(item["goodsId"] == cartItem.goodsId){
                  changeIndex = tempIndex;
              }
              tempIndex++;
       });
       //这里没有进行属性改变,是因为在点击勾选按钮的时候,已经对cartItem中的isCheck属性进行了改变
       tempList[changeIndex] = cartItem.toJson();
       cartString = json.encode(tempList).toString();
       prefs.setString(cartInfoKey, cartString);
       await getCartInfo();
   }


  //全选按钮操作
  changeAllCheckBtnState(bool isSelect) async{
        SharedPreferences prefs = await SharedPreferences.getInstance();
        cartString = prefs.getString(cartInfoKey);
        List<Map> tempList = (json.decode(cartString.toString()) as List).cast();
        List<Map> newList = [];
        for(var item in tempList){
           var newItem = item;
           newItem["isCheck"] = isSelect;
           newList.add(newItem);
        }
        cartString = json.encode(newList).toString();
        prefs.setString(cartInfoKey, cartString);
        await getCartInfo();

  }

  //商品数量加减
  addOrReduceAction(CartInfoModel cartItem,String todo) async {
      SharedPreferences prefs = await SharedPreferences.getInstance();
      cartString = prefs.getString(cartInfoKey);
      List<Map> tempList = (json.decode(cartString.toString()) as List).cast();
      int tempIndex = 0;
      int changeIndex = 0;
      tempList.forEach((item){
          if(item["goodsId"] == cartItem.goodsId){
               changeIndex = tempIndex;
          }
          tempIndex++;
      });
      if(todo == "add"){
          cartItem.count++;
      }else if(cartItem.count > 1){
          cartItem.count--;
      }
      tempList[changeIndex] = cartItem.toJson();
      cartString = json.encode(tempList).toString();
      prefs.setString(cartInfoKey, cartString);
      await getCartInfo();
  }


}

逻辑不算复杂,但是各个操作比较多,得细心点,SharedPreferences的使用很简单,key最好单独提出来写成一个常量。还有就是dart语言的基础还是有必要去简单过一下,不然有些东西就只知道写这个,不知道为什么要写这个东东。

购物车界面
import 'package:flutter/material.dart';
import 'package:provide/provide.dart';
import '../provide/cart_provide.dart';
import './cartSubWidget/cart_bottom.dart';
import './cartSubWidget/cart_item.dart';


class CarShopPage extends StatelessWidget {
  

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("购物车"),
        ),
        body: FutureBuilder(
          future: _getCartInfo(context),
          builder: (context,snapshot){
             if(snapshot.hasData){
                List cartList = Provide.value<CartProvide>(context).cartList;
                return Stack(
                    children: <Widget>[
                        Provide<CartProvide>(
                          builder: (context,child,value){
                              cartList = Provide.value<CartProvide>(context).cartList;
                              return ListView.builder(
                                itemCount: cartList.length,
                                itemBuilder: (context,index){
                                   return CartItem(cartList[index]);
                                },
                              );
                          },
                        ),
                        Positioned(
                          bottom: 0,
                          left: 0,
                          child: CartBottom(),
                        )
                    ],
                );
             }else{
                return Text("购物车是空的哦!");
             }
          },
        ),

    );
    
  }
  

  //数据加载(本地获取)
  Future<String> _getCartInfo(BuildContext context) async{
    await Provide.value<CartProvide>(context).getCartInfo();
    return "加载成功";
  }

}

注意对于要实时更新的UI一定要放在Provide widget中

购物车拆分widget

cartItem.dart

import 'package:flutter/material.dart';
import '../../model/cartInfo_model_entity.dart';
import './cart_count.dart';//加减组件
import 'package:provide/provide.dart';
import '../../provide/cart_provide.dart';

class CartItem extends StatelessWidget {
  final CartInfoModel item;
  CartItem(this.item);


  @override
  Widget build(BuildContext context) {
    return Container(
       margin: EdgeInsets.fromLTRB(5.0, 2.0, 5.0, 2.0),
       padding: EdgeInsets.fromLTRB(5.0, 10.0, 5.0, 10.0),
       decoration: BoxDecoration(
          color: Colors.white,
          border: Border(
             bottom: BorderSide(width: 1,color:Colors.black12)
          )
       ),
       child: Row(
         mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
             _cartCheckBtn(context,item),
             _cartImage(context,item),
             _cartGoodsName(context,item),
             _cartPrice(context,item),
          ],
       ),
    );
  }

  //选择按钮
  Widget _cartCheckBtn(BuildContext context,CartInfoModel item){
     return Container(
       width: MediaQuery.of(context).size.width / 10,
        child: Checkbox(
           value: item.isCheck,
           activeColor: Colors.orangeAccent,
           onChanged: (bool val){
             item.isCheck = val;//改变选中状态了
             Provide.value<CartProvide>(context).changeSelectState(item);
           },
        ), 
     );
  }
  //商品图片
  Widget _cartImage(BuildContext context,CartInfoModel item){
     return Container(
         width: MediaQuery.of(context).size.width / 5,
         padding: EdgeInsets.all(3.0),
         decoration: BoxDecoration(
            border: Border.all(width: 1,color: Colors.black12)
         ),
         child: Image.network(item.images),
     );
  }
  //商品名称
  Widget _cartGoodsName(BuildContext context,CartInfoModel item){
     return Container(
         width: MediaQuery.of(context).size.width / 10 * 4,
         padding: EdgeInsets.all(10.0),
         alignment: Alignment.topLeft,
         child: Column(
            children: <Widget>[
               Text(item.goodsName),
               SizedBox(height: 10.0,),
               CartCount(item)
               
            ],
         ),
     );
  }
  //商品价格
  Widget _cartPrice(BuildContext context,CartInfoModel item){
     return Container(
        width: MediaQuery.of(context).size.width / 10 * 2,
        alignment: Alignment.centerRight,
        child: Column(
           children: <Widget>[
              Text(
                "¥${item.price}",
                style: TextStyle(
                   color: Colors.orangeAccent
                ),
              ),
              SizedBox(height: 15.0,),
              Container(
                 child: InkWell(
                    onTap: (){
                       print("删除商品");
                       Provide.value<CartProvide>(context).deleteSingleGoods(item.goodsId);
                    },
                    child: Icon(
                       Icons.delete_forever,
                       color:Colors.black26,
                       size: 30.0,
                    ),
                 ),
              )
           ],
        ),
     );
  }



}

cartBottom.dart

import 'package:flutter/material.dart';
import 'package:provide/provide.dart';
import '../../provide/cart_provide.dart';

final double cartBottomFontS = 20.0;

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

  @override
  Widget build(BuildContext context) {
    return Container(
       padding: EdgeInsets.all(5.0),
       color: Colors.black12.withAlpha(15),
       child: Provide<CartProvide>(
          builder: (context,child,val){
             return Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    _selectAll(context),
                    _allPriceArea(context),
                    _goButton(context),
                  ],
              );
          },
       ) 
       
    );
  }

  //全选按钮
  Widget _selectAll(BuildContext context){
     return  Container(
          width:(MediaQuery.of(context).size.width - 10)/10 * 3,
          child: Row(
            children: <Widget>[
              Checkbox(
                  value: Provide.value<CartProvide>(context).isAllSelect,
                  activeColor: Colors.orangeAccent,
                  onChanged: (bool val){
                     print("点击了全选按钮");
                     Provide.value<CartProvide>(context).changeAllCheckBtnState(val);
                  },
                  
              ),
              Text("全选",
               style: TextStyle(
                  fontSize: cartBottomFontS
               ),
              )
            ],
          ),
      
     ); 
      
  }

  //合计区域
  Widget _allPriceArea(BuildContext context){
     double allPrice = Provide.value<CartProvide>(context).allPrice;
     return Container(
        width:(MediaQuery.of(context).size.width - 10)/10 * 4,
        child: Column(
          children: <Widget>[
            Row(
              children: <Widget>[
                 Container(
                    alignment: Alignment.centerRight,
                    
                    child: Text("合计:",
                          style: TextStyle(
                          fontSize: cartBottomFontS
                      ),
                    ),
                 ),
                 Container(
                    alignment: Alignment.centerLeft,
                    
                    child: Text(
                       "¥${allPrice}",
                       style:TextStyle(
                          color:Colors.orangeAccent,
                          fontSize: cartBottomFontS,
                       )
                    ),
                 )
              ],
            ),
           
          ],
        ), 
     
    );
  }
  
  //结算
  Widget _goButton(BuildContext context){
     int allGoodsCount = Provide.value<CartProvide>(context).allGoodsCount;
     return Container(
        width:(MediaQuery.of(context).size.width - 10)/10 *3,
        height: 40.0,
        padding: EdgeInsets.only(left: 10.0),
        child: InkWell(
          onTap: (){},
          child: Container(
            padding: EdgeInsets.all(10.0),
            alignment: Alignment.center,
            decoration: BoxDecoration(
               color:Colors.orangeAccent,
               borderRadius: BorderRadius.circular(5.0)
            ),
            child: Text(
              "结算(${allGoodsCount})",
              style:TextStyle(
                 color:Colors.white,
                 fontSize: 15.0,
              )
            ),
          ),
       ),
      
     );
  }

 

}

cartCount.dart

import 'package:flutter/material.dart';
import 'package:provide/provide.dart';
import '../../provide/cart_provide.dart';
import '../../model/cartInfo_model_entity.dart';

class CartCount extends StatelessWidget {
  CartInfoModel item;
  CartCount(this.item);
 

  @override
  Widget build(BuildContext context) {
    var cWidth = (MediaQuery.of(context).size.width) / 10 * 4 * 0.7;
    return Provide<CartProvide>(
        builder: (context,child,val){
            return Container(
              width: cWidth,
              margin: EdgeInsets.only(top: 5.0),
              decoration: BoxDecoration(
                  border: Border.all(width: 1,color: Colors.black12)
              ),
              child: Row(
                  children: <Widget>[
                    _reduceBtn(context,cWidth,item),
                    _countArea(context,cWidth,item),
                    _addBtn(context,cWidth,item)
                  ],
              ),
            );
        },
    ); 
    
  }

  //减少按钮
  Widget _reduceBtn(BuildContext context,cWidth,CartInfoModel cartItem){
     return InkWell(
        onTap: (){
          print("点击了减少按钮");
          Provide.value<CartProvide>(context).addOrReduceAction(cartItem, "reduce");
        },
        child: Container(
           width: cWidth / 4 +4,
           height: cWidth / 4 * 0.8,
           alignment: Alignment.center,
           decoration: BoxDecoration(
              color: item.count>1 ? Colors.white : Colors.black12,
              border: Border(
                 right: BorderSide(width: 1,color:Colors.black12)
              )
           ),
           child: Text("-"),
        ),

     );
  }

  //中间数字
  Widget _countArea(BuildContext context,cWidth,CartInfoModel cartItem){
    return Container(
       width: cWidth / 4 * 2 -10.0,
           height: cWidth / 4 * 0.8,
        alignment: Alignment.center,
        color: Colors.white,
        child: Text("${cartItem.count}"),
    );
  }


  //增加按钮
  Widget _addBtn(BuildContext context,cWidth,CartInfoModel cartItem){
     return InkWell(
        onTap: (){
          print("点击了加好按钮");
          Provide.value<CartProvide>(context).addOrReduceAction(cartItem, "add");
        },
        child: Container(
           width: cWidth / 4+4,
           height: cWidth / 4 * 0.8,
           alignment: Alignment.center,
           decoration: BoxDecoration(
              color: Colors.white,
              border: Border(
                 left: BorderSide(width: 1,color:Colors.black12)
              )
           ),
           child: Text("+"),
        ),

     );
  }

}
结尾:到此其实整个app最复杂的部分已经基本都完成了,剩下的一些我也许就不会放简书上了,等全部完成后,我会将项目上传到gitHub上。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容