• Flutter 商城实例 购物车


    开始制作购物车部分的内容了。这也算是最复杂的一个部分,也是我们基本掌握Flutter实战技巧的关键,当然我会还是采用UI代码和业务逻辑完全分开的形式,让代码完全解耦。

    1、购物车_添加商品

    Provide的建立

    因为要UI和业务进行分离,所以还是需要先建立一个Provide文件,在lib/provide/文件夹下,建立一个cart.dart文件。

    思路是先把List转换成字符串,然后再进行持久化,修改的时候再转换成LIst的l。

    先引入下面三个文件和包:

    import 'package:flutter/material.dart';
    import 'package:shared_preferences/shared_preferences.dart';
    import 'dart:convert';

    引进后建立一个类,并在里边写一个字符串变量(后期会换成对象)。代码如下:

    import 'package:flutter/material.dart';
    import 'package:shared_preferences/shared_preferences.dart';
    import 'dart:convert';
    
    class CartProvide with ChangeNotifier{
    
      String cartString="[]";
    
    }

    添加商品到购物车

    先来制作把商品添加到购物车的方法。思路是这样的,利用shared_preferences可以保存字符串的特点,我们先把List<Map>传换成字符串,然后操作的时候,我们再转换回来。说简单点就是持久化的只是一串字符串,然后需要操作的时候,我们变成List,操作List的每一项就可以了。

    save(goodsId,goodsName,count,price,images) async{
        //初始化SharedPreferences
        SharedPreferences prefs = await  SharedPreferences.getInstance();
        cartString=prefs.getString('cartInfo');  //获取持久化存储的值
        //判断cartString是否为空,为空说明是第一次添加,或者被key被清除了。
        //如果有值进行decode操作
        var temp=cartString==null?[]:json.decode(cartString.toString());
        //把获得值转变成List
        List<Map> tempList= (temp as List).cast();
        //声明变量,用于判断购物车中是否已经存在此商品ID
        var isHave= false;  //默认为没有
        int ival=0; //用于进行循环的索引使用
        tempList.forEach((item){//进行循环,找出是否已经存在该商品
          //如果存在,数量进行+1操作
          if(item['goodsId']==goodsId){
            tempList[ival]['count']=item['count']+1;=true;
          }
          ival++;
        });
        //  如果没有,进行增加
        if(!isHave){
          tempList.add({
            'goodsId':goodsId,
            'goodsName':goodsName,
            'count':count,
            'price':price,
            'images':images
          });
        }
        //把字符串进行encode操作,
        cartString= json.encode(tempList).toString();
        print(cartString);
        prefs.setString('cartInfo', cartString);//进行持久化
       
      }

    清空购物车

    为了测试方便,再顺手写一个清空购物车的方法,这个还没有谨慎思考,只是为了测试使用。

    remove() async{
        SharedPreferences prefs = await SharedPreferences.getInstance();
        //prefs.clear();//清空键值对
        prefs.remove('cartInfo');
        print('清空完成-----------------');
        notifyListeners();
      }

    注册全局依赖

    main.dart文件中注册全局依赖,先引入cart.dart文件.

    import './provide/cart.dart';

    然后在main区域进行声明

    var cartProvide = CartProvide();

    进行注入:

    ..provide(Provider<CartProvide>.value(cartProvide))

     业务逻辑加入到UI

    details_bottom.dart文件里,加入Provide,先进行引入。

    import 'package:provide/provide.dart';
    import '../../provide/cart.dart';
    import '../../provide/details_info.dart';

    然后声明provide的save方法中需要的参数变量。

    var goodsInfo = Provide.value<DetailsInfoProvide>(context).goodsInfo.data.goodInfo;
    var goodsId= goodsInfo.goodsId;
    var goodsName =goodsInfo.goodsName;
    var count =1;
    var price =goodsInfo.presentPrice;
    var images= goodsInfo.image1;

    然后在加入购物车的按钮的onTap方法中,加入下面代码.

    onTap: ()async {
      await Provide.value<CartProvide>(context).save(goodsID,goodsName,count,price,images);
     },

    先暂时把“马上结账”按钮方式清除购物车的方法,方便我们测试。

    onTap: ()async{
      await Provide.value<CartProvide>(context).remove();
    },

    做完这些,我们就要查看一下效果了,看看是否可以真的持久化。

    完成代码:

    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    import 'package:provide/provide.dart';
    import '../../provide/cart.dart';
    import '../../provide/detail_info.dart';
    
    class DetailsBottom extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        //声明变量,商品详细信息
        var goodsInfo = Provide.value<DetailsInfoProvide>(context).goodsInfo.data.goodInfo;
        var goodsId = goodsInfo.goodsId;
        var goodsName = goodsInfo.goodsName;
        var count = 1;
        var price = goodsInfo.presentPrice;
        var images = goodsInfo.image1;
    
        return Container(
           ScreenUtil().setWidth(750),
          height: ScreenUtil().setHeight(80),
          color: Colors.white,
          child: Row(
            children: <Widget>[
              InkWell(
                onTap: () {},
                child: Container(
                   ScreenUtil().setWidth(110),
                  alignment: Alignment.center,
                  child: Icon(Icons.shopping_cart,size: 35,color: Colors.red,),
                ),
              ),
              InkWell(
                onTap: () async{
                  await Provide.value<CartProvide>(context).save(goodsId, goodsName, count, price, images);
                },
                child: Container(
                   ScreenUtil().setWidth(320),
                  height: ScreenUtil().setHeight(80),
                  alignment: Alignment.center,
                  color: Colors.green,
                  child: Text(
                    '加入购物车',
                    style:TextStyle(color:Colors.white,fontSize:ScreenUtil().setSp(28))
                  ),
                ),
              ),
              InkWell(
                onTap: () async{
                  await Provide.value<CartProvide>(context).remove();
                },
                child: Container(
                   ScreenUtil().setWidth(320),
                  height: ScreenUtil().setHeight(80),
                  alignment: Alignment.center,
                  color: Colors.red,
                  child: Text(
                    '立即购买',
                    style:TextStyle(color:Colors.white,fontSize:ScreenUtil().setSp(28))
                  ),
                ),
              ),
            ],
          ),
        );
      }
    }

    2、购物车_建立数据模型

    刚才使用了字符串进行持久化,然后输出的时候都是Map,但是在真实工作中为了减少异常的发生,都要进行模型化处理,就是把Map转变为对象。

    建立模型文件

    得到的购物车数据,如下:

    {"goodsId":"2171c20d77c340729d5d7ebc2039c08d","goodsName":"五粮液52°500ml","count":1,"price":830.0,"images":"http://images.baixingliangfan.cn/shopGoodsImg/20181229/20181229211422_8507.jpg"}

    拷贝到自动生成mode的页面上,网址是:

    https://javiercbk.github.io/json_to_dart/

    生成后,在model文件夹下,建立一个新文件cartInfo.dart,然后把生成的mode文件进行改写,代码如下:

    class CartInfoModel {
      String goodsId;
      String goodsName;
      int count;
      double price;
      String images;
    
      CartInfoModel(
          {this.goodsId, this.goodsName, this.count, this.price, this.images});
    
      CartInfoModel.fromJson(Map<String, dynamic> json) {
        goodsId = json['goodsId'];
        goodsName = json['goodsName'];
        count = json['count'];
        price = json['price'];
        images = json['images'];
      }
    
      Map<String, dynamic> toJson() {
        final Map<String, dynamic> data = new Map<String, dynamic>();
        data['goodsId'] = this.goodsId;
        data['goodsName'] = this.goodsName;
        data['count'] = this.count;
        data['price'] = this.price;
        data['images'] = this.images;
        return data;
      }
    }

    这个相对于以前其它Model文件简单很多。也可以手写练习一下。

    在provide里使用模型

    有了模型文件之后,需要先引入provide里,然后进行改造。引入刚刚写好的模型层文件。

    import '../model/cartInfo.dart';

    provide类的最上部新声明一个List变量,这就是购物车页面用于显示的购物车列表了.

    List<CartInfoModel> cartList = [];

    然后改造save方法,让他支持模型类,但是要注意,原来的字符串不要改变,因为shared_preferences不持支对象的持久化。

    save(goodsId,goodsName,count,price,images) async{
        //初始化SharedPreferences
        SharedPreferences prefs = await  SharedPreferences.getInstance();
        cartString=prefs.getString('cartInfo');  //获取持久化存储的值
        //判断cartString是否为空,为空说明是第一次添加,或者被key被清除了。
        //如果有值进行decode操作
        var temp=cartString==null?[]:json.decode(cartString.toString());
        //把获得值转变成List
        List<Map> tempList= (temp as List).cast();
        //声明变量,用于判断购物车中是否已经存在此商品ID
        var isHave= false;  //默认为没有
        int ival=0; //用于进行循环的索引使用
        tempList.forEach((item){//进行循环,找出是否已经存在该商品
          //如果存在,数量进行+1操作
          if(item['goodsId']==goodsId){
            tempList[ival]['count']=item['count']+1;
             //关键代码-----------------start
            cartList[ival].count++;
             //关键代码-----------------end
            isHave=true;
          }
          ival++;
        });
        // 如果没有,进行增加
        if(!isHave){
           //关键代码-----------------start
              Map<String, dynamic> newGoods={
                 'goodsId':goodsId,
                'goodsName':goodsName,
                'count':count,
                'price':price,
                'images':images
              };
              tempList.add(newGoods);
              cartList.add(new CartInfoModel.fromJson(newGoods));
           //关键代码-----------------end
        }
        //把字符串进行encode操作,
        cartString= json.encode(tempList).toString();
        print('字符串>>>>>>>>>>>${cartString}');
        print('数据模型>>>>>>>>>>>${cartList}');
        print(cartList.toString());
        prefs.setString('cartInfo', cartString);//进行持久化
        notifyListeners();
      }

    得到购物车中商品方法

    有了增加方法,我们还需要写一个得到购物车中的方法,现在就学习一下结合Model如何得到持久化的数据。

    //得到购物车中的商品
      getCartInfo() async {
         SharedPreferences prefs = await SharedPreferences.getInstance();
         //获得购物车中的商品,这时候是一个字符串
         cartString=prefs.getString('cartInfo'); 
         //把cartList进行初始化,防止数据混乱 
         cartList=[];
         //判断得到的字符串是否有值,如果不判断会报错
         if(cartString==null){
           cartList=[];
         }else{
           List<Map> tempList= (json.decode(cartString.toString()) as List).cast();
           tempList.forEach((item){
              cartList.add(new CartInfoModel.fromJson(item));
           });
    
         }
          notifyListeners();
      }

     cart.dart完整代码:

    import 'package:flutter/material.dart';
    import 'package:shared_preferences/shared_preferences.dart';
    import 'dart:convert';
    import '../model/cartInfo.dart';
    
    class CartProvide with ChangeNotifier{
      String cartString = '[]'; //声明一个变量 做持久化的存储
      List<CartInfoModel> cartList = [];
    
      //加入购物车
      save(goodsId, goodsName, count, price, images) async{
        SharedPreferences prefs = await SharedPreferences.getInstance(); //初始化
        cartString = prefs.getString('cartInfo'); //获取持久化存储的值
        //判断cartString是否为空,为空说明是第一次添加,或者被key被清除了。
        //如果有值进行decode操作
        var temp = cartString == null ? [] : json.decode(cartString.toString());
        //把获得值转变成List
        List<Map> tempList = (temp as List).cast(); //temp转化为List
        //声明变量,用于判断购物车中是否已经存在此商品ID
        bool isHave = false; //默认为没有
        int ival = 0; //用于进行循环的索引使用
        tempList.forEach((item){ //进行循环,找出是否已经存在该商品
          //如果存在,数量进行+1操作
          if(item['goodsId'] == goodsId){
            tempList[ival] ['count'] = item['count'] +1;
            cartList[ival].count++;
            isHave = true;
          }
          ival++;
        });
        //如果没有,进行增加
        if(!isHave){
          Map<String, dynamic> newGoods = {
            'goodsId':goodsId,
            'goodsName':goodsName,
            'count':count,
            'price':price,
            'images':images,
          };
          tempList.add(newGoods);
          cartList.add(CartInfoModel.fromJson(newGoods)); //fromJson变成对象
        }
        //把字符串进行encode操作
        cartString = json.encode(tempList).toString(); //json数据转字符串
        print('字符串>>>>>>>>>>>${cartString}');
        print('数据模型>>>>>>>>>>>${cartList}');
        prefs.setString('cartInfo', cartString); //进行持久化
        notifyListeners(); //通知
      }
      //清空购物车
      remove() async{
        SharedPreferences prefs=await SharedPreferences.getInstance(); //初始化
        prefs.remove('cartInfo');
        cartList = [];
        print('清空完成------------');
        notifyListeners(); //通知
      }
      //得到购物车中的商品
      getCartInfo() async{
        SharedPreferences prefs=await SharedPreferences.getInstance(); //初始化
        //获得购物车中的商品,这时候是一个字符串
        cartString = prefs.getString('cartInfo');
        //把cartList进行初始化,防止数据混乱 
        cartList = [];
        //判断得到的字符串是否有值,如果不判断会报错
        if(cartString == null){
          cartList = [];
        }else{
          List<Map> tempList = (json.decode(cartString.toString()) as List).cast(); //字符串转为List<Map>类型
          tempList.forEach((item){
            cartList.add(CartInfoModel.fromJson(item)); //json转成对象,加入到cartList中
          });
        }
        notifyListeners(); //通知
      }
    
    }

    3、购物车_大体结构布局

    下面开始制作页面。其实在实际开发中也有很多这样的情况。就是先得到数据,再调试页面。

    页面基本结构搭建

    cart_page.dart清空原来写的持久化的代码。建立页面的基本接口,还是使用脚手架组件Scaffold来进行操作。代码如下:

    import 'package:flutter/material.dart';
    import 'package:shared_preferences/shared_preferences.dart';
    import 'package:provide/provide.dart';
    import '../provide/cart.dart';
    
    class CartPage extends StatelessWidget {
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('购物车'),
          ),
          body:Text('测试')
        );
      }
    }

    Future方法编写(创建Future方法获取购物车持久化数据)

    使用了Future组件,自然需要一个返回Future的方法了,在这个方法里,我们使用Provide取出本地持久化的数据,然后进行变化。

    Future<String> _getCartInfo(BuildContext context) async{
         await Provide.value<CartProvide>(context).getCartInfo();
         return 'end';
     }

    再body区域我们使用Future Widget,因为就算是本地持久化,还是有一个时间的,当然这个时间可能你肉眼看不见。不过这样控制台可能会把错误信息返回回来。

    body: FutureBuilder(
        future:_getCartInfo(context),
        builder: (context,snapshot){
    
          if(snapshot.hasData){
            List cartList=Provide.value<CartProvide>(context).cartList;
          }else{
            return Text('正在加载');
          }
        },
      ),
      );
    }

    用ListView简单输出

    return ListView.builder(
      itemCount: cartList.length,
      itemBuilder: (context,index){
        return ListTile(
          title:Text(cartList[index].goodsName)
        );
      },
    );

    现在可以简单的进行预览,当然页面还是很丑的,下面会继续进行美化。会把列表的子项单独拿出一个文件,这样会降低以后的维护成本。 

    4、购物车_商品列表子项组件编写

    上面把购物车页面的大体结构编写好,并且也可以获得购物车中的商品列表信息了,但是页面不够美观,现在继续完成子项的UI美化。

    编写购物车单项方法

    为了以后维护方便,我们还是采用单独编写的方式,把购物车里边的每一个子项统一作一个组件出来。

    现在libpages下建立一个新文件夹cart_page,然后在新文件夹下面家里一个cart_item.dart文件。先引入几个必要的文件.

    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    import '../../model/cartInfo.dart';

    然后声明一个stateLessWidget 类,名字叫CartItem并设置接收参数,这里的接收参数就是cartInfo对象,也就是每个购物车商品的子项。代码如下:

    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    import '../../model/cartInfo.dart';
    
    class CartItem extends StatelessWidget {
      final CartInfoMode item;
      CartItem(this.item);
    
      @override
      Widget build(BuildContext context) {
        print(item);
        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(1,color:Colors.black12)
              )
            ),
            child: Row(
              children: <Widget>[
              
              ],
            ),
          );
      }
    
    }

    编写多选按钮方法

    //多选按钮
      Widget _cartCheckBt(item){
        return Container(
          child: Checkbox(
            value: true,
            activeColor:Colors.pink,
            onChanged: (bool val){},
          ),
        );
      }

    编写商品图片方法

    //商品图片 
      Widget _cartImage(item){
        return Container(
           ScreenUtil().setWidth(150),
          padding: EdgeInsets.all(3.0),
          decoration: BoxDecoration(
            border: Border.all( 1,color:Colors.black12)
          ),
          child: Image.network(item.images),
        );
      }

    编写商品名称方法

    //商品名称
      Widget _cartGoodsName(item){
        return Container(
           ScreenUtil().setWidth(300),
          padding: EdgeInsets.all(10),
          alignment: Alignment.topLeft,
          child: Column(
            children: <Widget>[
              Text(item.goodsName)
            ],
          ),
        );
      }

    编写商品价格方法

    //商品价格
      Widget _cartPrice(item){
        return Container(
            ScreenUtil().setWidth(150) ,
            alignment: Alignment.centerRight,
            
            child: Column(
              children: <Widget>[
                Text('¥${item.price}'),
                Container(
                  child: InkWell(
                    onTap: (){},
                    child: Icon(
                      Icons.delete_forever,
                      color: Colors.black26,
                      size: 30,
                    ),
                  ),
                )
              ],
            ),
          );
      }

    进行整合

    child: Row(
      children: <Widget>[
        _cartCheckBt(item),
        _cartImage(item),
        _cartGoodsName(item),
        _cartPrice(item)
      ],
    ),

    全部代码如下:

    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    import '../../model/cartInfo.dart';
    
    class CartItem extends StatelessWidget {
      final CartInfoModel item; 
      CartItem(this.item); //构造方法接收参数
    
      @override
      Widget build(BuildContext context) {
        print(item);
        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( 1,color: Colors.black12)
            )
          ),
          child: Row(
            children: <Widget>[
              _cartCheckBt(item),
              _cartImage(item),
              _cartGoodsName(item),
              _cartPirce(item),
            ],
          ),
        );
      }
    
      //多选框
      Widget _cartCheckBt(item){
        return Container(
          child: Checkbox(
            value: true, //先默认选中
            activeColor:Colors.red, //选中颜色
            onChanged: (bool val){}, 
          ),
        );
      }
      //商品图片
      Widget _cartImage(item){
        return Container(
           ScreenUtil().setWidth(150),
          padding: EdgeInsets.all(3.0),
          decoration: BoxDecoration(
            border: Border.all( 1,color: Colors.black12)
          ),
          child: Image.network(item.images),
        );
      }
      //商品名称
      Widget _cartGoodsName(item){
        return Container(
           ScreenUtil().setWidth(300),
          padding: EdgeInsets.all(10),
          alignment: Alignment.topLeft,
          child: Column(
            children: <Widget>[
              Text(item.goodsName),
    
            ],
          ),
        );
      }
      //商品价格
      Widget _cartPirce(item){
        return Container(
           ScreenUtil().setWidth(150),
          alignment: Alignment.centerRight,
          child: Column(
            children: <Widget>[
              Text('¥${item.price}'),
              Container(
                child: InkWell(
                  onTap: (){},
                  child: Icon(Icons.delete_forever,color: Colors.black26,size: 30),
                ),
              )
            ],
          ),
        );
      }
    
    }

    然后在cart_page.dart页面引入cart_item.dart

    import './cart_page/cart_item.dart';

    修改代码:

    // return ListTile(
    //   title: Text(cartList[index].goodsName),
    // );
    return CartItem(cartList[index]);

    运行后可以查看效果。

    5、购物车_制作底部结算栏的UI

    这个要使用Stack Widget

    建立底部结算栏页面

    lib/pages/cart_page文件夹下,新建一个cart_bottom.dart文件。文件建立好以后,先引入下面的基础package

    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';

    引入完成后,用快捷的方式建立一个StatelessWidget,建立后,我们使用Row来进行总体布局,并给Container一些必要的修饰.代码如下:

    class CartBottom extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: EdgeInsets.all(5.0),
          color: Colors.white,
           ScreenUtil().setWidth(750),
          child: Row(
            children: <Widget>[
            
            ],
          ),
        );
      }
    }

    这就完成了一个底部结算栏的大体结构确定,大体结构完成后,我们还是把里边的细节,拆分成不同的方法返回对象的组件。

    全选按钮方法

    先来制作全选按钮方法,这个外边采用Container,里边使用了一个Row,这样能很好的完成横向布局的需求.

    //全选按钮
      Widget selectAllBtn(){
        return Container(
          child: Row(
            children: <Widget>[
              Checkbox(
                value: true,
                activeColor: Colors.pink,
                onChanged: (bool val){},
              ),
              Text('全选')
            ],
          ),
        );
      }

    合计区域方法

    合计区域由于布局对齐方式比较复杂,所以这段代码虽然很简单,但是代码设计的样式比较多,需要你有很好的样式编写能力.代码如下:

     // 合计区域
     Widget allPriceArea(){
        return Container(
           ScreenUtil().setWidth(430),
          alignment: Alignment.centerRight,
          child: Column(
            children: <Widget>[
              Row(
                children: <Widget>[
                  Container(
                    alignment: Alignment.centerRight,
                     ScreenUtil().setWidth(280),
                    child: Text(
                      '合计:',
                      style:TextStyle(
                        fontSize: ScreenUtil().setSp(36)
                      )
                    ), 
                  ),
                  Container(
                     alignment: Alignment.centerLeft,
                      ScreenUtil().setWidth(150),
                     child: Text(
                      '¥1922',
                      style:TextStyle(
                        fontSize: ScreenUtil().setSp(36),
                        color: Colors.red,
                      )
                    ),
                  )
                  
                ],
              ),
              Container(
                 ScreenUtil().setWidth(430),
                alignment: Alignment.centerRight,
                child: Text(
                  '满10元免配送费,预购免配送费',
                  style: TextStyle(
                    color: Colors.black38,
                    fontSize: ScreenUtil().setSp(22)
                  ),
                ),
              )
              
            ],
          ),
        );
    
      }

    结算按钮方法

    这个方法里边的按钮,我们并没有使用Flutter Button Widget 而是使用InkWell自己制作一个组件。这样作能很好的控制按钮的形状,还可以解决水波纹的问题,一举两得。代码如下:

    //结算按钮
      Widget goButton(){
        return Container(
           ScreenUtil().setWidth(160),
          padding: EdgeInsets.only(left: 10),
          child:InkWell(
            onTap: (){},
            child: Container(
              padding: EdgeInsets.all(10.0),
              alignment: Alignment.center,
              decoration: BoxDecoration(
                 color: Colors.red,
                 borderRadius: BorderRadius.circular(3.0)
              ),
              child: Text(
                '结算(6)',
                style: TextStyle(
                  color: Colors.white
                ),
              ),
            ),
          ) ,
        );
        
      }

    加入到页面中

    组件样式基本都各自完成后,接下来就是组合和加入到页面中了,我们先把个个方法组合到底部结算区域,也就是放到build方法里。

    Widget build(BuildContext context) {
        return Container(
          margin: EdgeInsets.all(5.0),
          color: Colors.white,
           ScreenUtil().setWidth(750),
          child: Row(
            children: <Widget>[
              selectAllBtn(),
              allPriceArea(),
              goButton()
            ],
          ),
        );
      }

    完成后就是到lib/pages/cart_page.dart文件中,加入底部结算栏的操作了,这里我们需要使用Stack Widget组件。

    首先需要引入cart_bottom.dart

    import './cart_page/cart_bottom.dart';

    然后改写FutureBuilder Widget里边的builder方法,这时候返回的是一个Stack Widget。代码如下:

    import 'package:flutter/material.dart';
    import 'package:provide/provide.dart';
    import '../provide/cart.dart';
    import './cart_page/cart_item.dart';
    import './cart_page/cart_bottom.dart';
    
    class CartPage extends StatelessWidget {
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('购物车'),
          ),
          body: FutureBuilder(
            future:_getCartInfo(context),
            builder: (context,snapshot){
              if(snapshot.hasData && cartList!=null){
                List cartList=Provide.value<CartProvide>(context).cartList;
                //关键代码-------------------start
                return Stack(
                  children: <Widget>[
                    ListView.builder(
                      itemCount: cartList.length,
                      itemBuilder: (context,index){
                        return CartItem(cartList[index]);
                      },
                    ),
                    Positioned(
                      bottom:0,
                      left:0,
                      child: CartBottom(),
                    )
                  ],
                );
                //关键代码-----------------end
              
              }else{
                return Text('正在加载');
              }
            },
          ),
        );
      }
    
      Future<String> _getCartInfo(BuildContext context) async{
         await Provide.value<CartProvide>(context).getCartInfo();
         return 'end';
      }
      
    }

    这步做完之后,就可以进行预览了。可以试着把这个页面布局成自己想要的样子。

    6、购物车_制作数量加减按钮UI

    购物车的UI界面已经基本完成了,只差最后一个数量加载的部分没有进行布局,现在就把这个部分的布局制作完成。

    建立组件和基本结构

    lib/pages/cart_page/文件夹下,建立一个新的文件cart_count.dart。先引入两个布局使用的基本文件。

    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    
    class CartCount extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          
        );
      }
    }

    然后开始写基本结构,我们这里使用ContainerRow的形式。

    Widget build(BuildContext context) {
        return Container(
           ScreenUtil().setWidth(165),
          margin: EdgeInsets.only(top:5.0),
          decoration: BoxDecoration(
            border:Border.all( 1 , color:Colors.black12)
          ),
          child: Row(
            children: <Widget>[
            ],
          ),
        );
      }

    写完这个,再把Row里边的每个子元素进行拆分.

    减少按钮UI编写

    // 减少按钮
      Widget _reduceBtn(){
        return InkWell(
          onTap: (){},
          child: Container(
             ScreenUtil().setWidth(45),
            height: ScreenUtil().setHeight(45),
            alignment: Alignment.center,
           
            decoration: BoxDecoration(
              color: Colors.white,
              border:Border(
                right:BorderSide(1,color:Colors.black12)
              )
            ),
            child: Text('-'),
          ),
        );
      }

    添加按钮UI编写

    //添加按钮
      Widget _addBtn(){
        return InkWell(
          onTap: (){},
          child: Container(
             ScreenUtil().setWidth(45),
            height: ScreenUtil().setHeight(45),
            alignment: Alignment.center,
           
             decoration: BoxDecoration(
              color: Colors.white,
              border:Border(
                left:BorderSide(1,color:Colors.black12)
              )
            ),
            child: Text('+'),
          ),
        );
      }

    数量区域UI编写

    //中间数量显示区域
      Widget _countArea(){
        return Container(
           ScreenUtil().setWidth(70),
          height: ScreenUtil().setHeight(45),
          alignment: Alignment.center,
          color: Colors.white,
           child: Text('1'),
        );
      }

    进行组合

    组件都写好后,要进行组合和加入到页面中的操作。

    组合:直接在build区域的Row数组中进行组合。

     Widget build(BuildContext context) {
        return Container(
           ScreenUtil().setWidth(165),
          margin: EdgeInsets.only(top:5.0),
          decoration: BoxDecoration(
            border:Border.all( 1 , color:Colors.black12)
          ),
          child: Row(
            //关键代码----------------start
            children: <Widget>[
              _reduceBtn(),
              _countArea(),
              _addBtn(),
            ],
            //关键代码----------------end
          ),
          
        );
      }

    这个不完成后,再到同级目录下的cart_item.dart,引入和使用。先进行文件的引入.

    import './cart_count.dart';

    引入后,再商品名称的方法中直接引入就。

    //商品名称
      Widget _cartGoodsName(item){
        return Container(
           ScreenUtil().setWidth(300),
          padding: EdgeInsets.all(10),
          alignment: Alignment.topLeft,
          child: Column(
            children: <Widget>[
              Text(item.goodsName),
              //关键代码---------start
              CartCount()
              //关键代码---------end
            ],
          ),
        );
      }

    完成后就可以进行预览了。

    7、购物车_在Model中增加选中字段

    通过布局,我们可以看到是有选中和多选操作的,但是在设计购物车模型时并没有涉及这个操作,现在修改一下。

    修改Model文件

    首先我们打开lib/model/cartInfo.dart文件,增加一个新的变量isCheck

    class CartInfoMode {
      String goodsId;
      String goodsName;
      int count;
      double price;
      String images;
      //------新添加代码----start
      bool isCheck;
      //------新添加代码----end
    
      CartInfoMode(
          //需要修改---------start-----
          {this.goodsId, this.goodsName, this.count, this.price, this.images,this.isCheck});
          //修改需改--------end------
    
      CartInfoMode.fromJson(Map<String, dynamic> json) {
        goodsId = json['goodsId'];
        goodsName = json['goodsName'];
        count = json['count'];
        price = json['price'];
        images = json['images'];
        //------新添加代码----start
        isCheck = json['isCheck'];
        //------新添加代码----end
      }
    
      Map<String, dynamic> toJson() {
        final Map<String, dynamic> data = new Map<String, dynamic>();
        data['goodsId'] = this.goodsId;
        data['goodsName'] = this.goodsName;
        data['count'] = this.count;
        data['price'] = this.price;
        data['images'] = this.images;
        //------新添加代码----start
        data['isCheck']= this.isCheck;
        /------新添加代码----end
        return data;
      }
    }

    在增加时加入isCheck

    打开lib/provide/cart.dart文件,找到添加购物车商品的方法save,修改增加的部分代码。

    Map<String, dynamic> newGoods={
      'goodsId':goodsId,
      'goodsName':goodsName,
      'count':count,
      'price':price,
      'images':images,
      //-----新添加代码-----start
      'isCheck': true  //是否已经选择
      //-----新添加代码-----end
    };

    修改UI的值

    之前UI中多选按钮的值,我们是写死的,现在就可以使用这个动态的值了。打开lib/pages/cart_page/cart_item.dart文件,找到多选按钮的部分,修改val的值.

    Widget _cartCheckBt(context,item){
      return Container(
        child: Checkbox(
          //修改部分--------start----
          value: item.isCheck,
          //修改部分--------end------
          activeColor:Colors.pink,
          onChanged: (bool val){
          },
        ),
      );
    }

    记得修改完成后,要把原来的持久化购物车的数据清除掉,删除掉后再次填入新的商品到购物车,就可以正常显示了。

    8、购物车_删除单个商品功能制作

    页面终于制作完成了,剩下来就是逐步完善购物车中的各项功能,这部分的视频可能拆分的比较细致。

    编写删除方法

    直接在provide中的cart.dart文件里,增加一个deleteOneGoods方法。编写思路是这样的,先从持久化数据里得到数据,然后把纯字符串转换成字List,转换之后进行循环,如果goodsId,相同,说明就是要删除的项,把索引进行记录,记录之后用removeAt方法进行删除,删除后再次进行持久化,并重新获得数据。 主要代码如下:

    //删除单个购物车商品
      deleteOneGoods(String goodsId) async{
        SharedPreferences prefs = await SharedPreferences.getInstance(); //初始化
        cartString = prefs.getString('cartInfo'); //获取持久化
        List<Map> tempList = (json.decode(cartString.toString()) as List).cast(); //字符串转为List<Map>类型
        int tempIndex = 0; //循环使用的索引
        int delIndex = 0; //删除第几个的索引
        tempList.forEach((item){
          if(item['goodsId'] == goodsId){
            delIndex = tempIndex;
          }
          tempIndex++;
        });  
    
        tempList.removeAt(delIndex); //删除索引项
        cartString = json.encode(tempList).toString(); //转换cartString持久化
        prefs.setString('cartInfo', cartString); //修改cartString持久化
        await getCartInfo(); //持久化成功后刷新列表
    
      }

    注意,这个部分为什么循环时不进行删除,因为dart语言不支持迭代时进行修改,这样可以保证在循环时不出错。

    修改UI界面,实现效果

    UI界面主要时增加Proivde组件,就是当值法伤变化时,界面也随着变化。打开cart_page.dart文件,主要修改build里的ListView区域,代码如下:

    import 'package:flutter/material.dart';
    import 'package:provide/provide.dart';
    import '../provide/cart.dart';
    import './cart_page/cart_item.dart';
    import './cart_page/cart_bottom.dart';
    
    class CartPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('购物车'),
          ),
          body: FutureBuilder(
            future:_getCartInfo(context),
            builder: (context,snapshot){
              List cartList=Provide.value<CartProvide>(context).cartList;
              if(snapshot.hasData && cartList!=null){
                  return Stack(
                    children: <Widget>[
                      //主要代码--------------------start--------
                      Provide<CartProvide>(
                        builder: (context,child,childCategory){
                           cartList= Provide.value<CartProvide>(context).cartList;
                          print(cartList);
                          return ListView.builder(
                            itemCount: cartList.length,
                            itemBuilder: (context,index){
                              return CartItem(cartList[index]);
                            },
                          );
                        }
                      ), 
                      //主要代码------------------end---------
                      Positioned(
                        bottom:0,
                        left:0,
                        child: CartBottom(),
                      )
                    ],
                  );
    
              }else{
                return Text('正在加载');
              }
            },
          ),
        );
      }
    
      Future<String> _getCartInfo(BuildContext context) async{
         await Provide.value<CartProvide>(context).getCartInfo();
         return 'end';
      }
    
    }

    增加删除响应事件

    cart_item.dart文件中,增加删除响应事件,由于所有业务逻辑都在Provide中,所以需要引入下面两个文件。

    import 'package:provide/provide.dart';
    import '../../provide/cart.dart';

    有了这两个文件后,可以修改对应的方法_cartPrice。首先要加入context选项,然后修改里边的onTap方法。具体代码如下:

    //商品价格
      Widget _cartPrice(context,item){
        return Container(
            ScreenUtil().setWidth(150) ,
            alignment: Alignment.centerRight,
            child: Column(
              children: <Widget>[
                Text('¥${item.price}'),
                Container(
                  child: InkWell(
                    onTap: (){
                      //主要代码---------------start----------
                      Provide.value<CartProvide>(context).deleteOneGoods(item.goodsId);
                      //主要代码--------------end-----------
                    },
                    child: Icon(
                      Icons.delete_forever,
                      color: Colors.black26,
                      size: 30,
                    ),
                  ),
                )
              ],
            ),
          );
      }

    这步做完,已经有了删除功能,可以进行测试了.。

    9、购物车_计算商品价格和数量

    购物车中都有自动计算商品价格和商品数量的功能,下面就把这两个小功能实现一下。

    增加Provide变量

    lib/provide/cart.dart文件的类头部,增加总价格allPrice和总商品数量allGoodsCount两个变量.

    class CartProvide with ChangeNotifier{
    
      String cartString="[]";
      List<CartInfoMode> cartList=[]; //商品列表对象
      //新代码----------start
      double allPrice =0 ;   //总价格
      int allGoodsCount =0;  //商品总数量
      
      ...

    修改getCartInfo()方法

    主要是在循环是累计增加数量和价格,这里给出全部增加的代码,并标注了修改部分。

    getCartInfo() async {
         SharedPreferences prefs = await SharedPreferences.getInstance();
         //获得购物车中的商品,这时候是一个字符串
         cartString=prefs.getString('cartInfo'); 
         
         //把cartList进行初始化,防止数据混乱 
         cartList=[];
         //判断得到的字符串是否有值,如果不判断会报错
         if(cartString==null){
           cartList=[];
         }else{
           List<Map> tempList= (json.decode(cartString.toString()) as List).cast();
            //---------修改代码------start-------------
           allPrice=0;
           allGoodsCount=0;
            //---------修改代码------end-------------
    
           tempList.forEach((item){
               //---------修改代码------start-------------
              if(item['isCheck']){
                 allPrice += (item['count']*item['price']);
                 allGoodsCount += item['count'];
              }
               //---------修改代码------end-------------
             
              cartList.add(new CartInfoMode.fromJson(item));
    
           });
    
         }
          notifyListeners();
      }

    修改UI界面 显示结果

    有了业务逻辑,就应该可以正常的显示出界面效果了。但是需要把原来我们写死的值,都改成动态的。

    打开lib/pages/cart_page/cart_bottom.dart文件,先用import引入provide package

    import 'package:provide/provide.dart';
    import '../../provide/cart.dart';

    然后把底部的三个区域方法都加上context上下文参数,因为Provide的使用,必须有上下文参数。

    Widget build(BuildContext context) {
        return Container(
          margin: EdgeInsets.all(5.0),
          color: Colors.white,
           ScreenUtil().setWidth(750),
          child: Provide<CartProvide>(
            builder: (context,child,childCategory){
              return  Row(
                children: <Widget>[
                  //修改部分--------start----------
                  selectAllBtn(context),
                  allPriceArea(context),
                  goButton(context)
                  //修改部分--------end-----------
                ],
              );
            },
          )
        );
      }

    然后在两个方法中都从Provide里动态获取变量,就可以实现效果了。

    合计区域的方法修改代码:

    // 合计区域
      Widget allPriceArea(context){
        //修改代码---------------start------------
        double allPrice = Provide.value<CartProvide>(context).allPrice;
        //修改代码---------------end------------
        return Container(
           ScreenUtil().setWidth(430),
          alignment: Alignment.centerRight,
          child: Column(
            children: <Widget>[
              Row(
                children: <Widget>[
                  Container(
                    alignment: Alignment.centerRight,
                     ScreenUtil().setWidth(280),
                    child: Text(
                      '合计:',
                      style:TextStyle(
                        fontSize: ScreenUtil().setSp(36)
                      )
                    ), 
                  ),
                  Container(
                     alignment: Alignment.centerLeft,
                     ScreenUtil().setWidth(150),
                     //修改代码---------------start------------
                    child: Text(
                      '¥${allPrice}',
                      style:TextStyle(
                        fontSize: ScreenUtil().setSp(36),
                        color: Colors.red,
                      )
                    ),
                     //修改代码---------------end------------
                    
                  )
                ],
              ),
              Container(
                 ScreenUtil().setWidth(430),
                alignment: Alignment.centerRight,
                child: Text(
                  '满10元免配送费,预购免配送费',
                  style: TextStyle(
                    color: Colors.black38,
                    fontSize: ScreenUtil().setSp(22)
                  ),
                ),
              )
            ],
          ),
        );
      }

    结算按钮区域修改代码:

    //结算按钮
      Widget goButton(context){
        //修改代码---------------start------------
        int allGoodsCount =  Provide.value<CartProvide>(context).allGoodsCount;
        //修改代码---------------end--------------
        return Container(
           ScreenUtil().setWidth(160),
          padding: EdgeInsets.only(left: 10),
          child:InkWell(
            onTap: (){},
            child: Container(
              padding: EdgeInsets.all(10.0),
              alignment: Alignment.center,
              decoration: BoxDecoration(
                 color: Colors.red,
                 borderRadius: BorderRadius.circular(3.0)
              ),
              //修改代码---------------start------------
              child: Text(
                '结算(${allGoodsCount})',
                style: TextStyle(
                  color: Colors.white
                ),
              ),
              //修改代码---------------end------------
            ),
          ) ,
        );
      }

    点击删除,总价和商品的数量还没有变化。

    这是因为没有包裹provide 的原因,我们需要把这里的Row嵌套到Provide里面去

    class CartBottom extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          padding:EdgeInsets.all(5.0),
          color:Colors.white,
          //修改代码---------------start------------
          child:Provide<CartProvide>(
            builder: (context,child,val){
              return Row(
                children:<Widget>[
                  selectAllBtn(context),
                  allPriceArea(context),
                  goButton(context),
                ]
              );
            },
          ),
          //修改代码---------------end------------
        );
      }

    这步完成后,就应该可以正常动态显示购物车中的商品数量和商品价格了。

    10、购物车_商品选中功能制作

    在购物车里是有选择和取消选择,还有全选的功能按钮的。当我们选择时,价格和数量都是跟着自动计算的,列表也是跟着刷新的。这节课主要完成单选和全选按钮的交互效果。

    制作商品单选按钮的交效果

    这些业务逻辑代码,当然需要写到Provide中,打开lib/provide/cart.dart文件。新建一个changeCheckState方法:

    changeCheckState(CartInfoMode cartItem) async{
         SharedPreferences prefs = await SharedPreferences.getInstance();
         cartString=prefs.getString('cartInfo');  //得到持久化的字符串
         List<Map> tempList= (json.decode(cartString.toString()) as List).cast(); //声明临时List,用于循环,找到修改项的索引
         int tempIndex =0;  //循环使用索引
         int changeIndex=0; //需要修改的索引
         tempList.forEach((item){
             
             if(item['goodsId']==cartItem.goodsId){
              //找到索引进行赋值
              changeIndex=tempIndex;
             }
             tempIndex++;
         });
       //tempList里面是Map,而传递过来的cartItem里是对象 tempList[changeIndex]
    =cartItem.toJson(); //把对象变成Map值 cartString= json.encode(tempList).toString(); //变成字符串 prefs.setString('cartInfo', cartString);//进行持久化 await getCartInfo(); //重新读取列表 }

    注意,这个部分为什么循环时不进行修改,因为dart语言不支持迭代时进行修改,就是说循环时不允许修改循环项里的所有属性,这样可以保证在循环时不出错。

    业务逻辑写完后到到UI层进行修改,打开lib/pages/cart_page/cart_item.dart文件,修改多选按钮的onTap方法。

    //多选按钮
      Widget _cartCheckBt(context,item){
        return Container(
          child: Checkbox(
            value: item.isCheck, //是否选中状态 
            activeColor:Colors.pink,
            //-------新增代码--------start---------
            onChanged: (bool val){
              item.isCheck = val;
              Provide.value<CartProvide>(context).changeCheckState(item);
            },
            //-------新增代码--------end---------
          ),
        );
      }

    修改完成后,可以点击测试一下效果,如果一切正常,就可以进行选中和取消的交互了。

    全选按钮交互效果制作

    在cart.dart文件中声明一个状态变量isAllCheck,然后在读取购物车商品数据时进行更改。

    bool isAllCheck= true; //是否全选

     修改getCartInfo方法,就是获取购物车列表的方法.

     //得到购物车中的商品
      getCartInfo() async {
         SharedPreferences prefs = await SharedPreferences.getInstance();
         //获得购物车中的商品,这时候是一个字符串
         cartString=prefs.getString('cartInfo'); 
         //把cartList进行初始化,防止数据混乱 
         cartList=[];
         //判断得到的字符串是否有值,如果不判断会报错
         if(cartString==null){
           cartList=[];
         }else{
           List<Map> tempList= (json.decode(cartString.toString()) as List).cast();
           allPrice=0;
           allGoodsCount=0;
           //--------新增代码----------start--------
           isAllCheck=true; //全选初始化默认true
           //--------新增代码----------end--------
           tempList.forEach((item){
               //--------新增代码----------start--------
              if(item['isCheck']){
                 allPrice+=(item['count']*item['price']);
                 allGoodsCount+=item['count'];
              }else{
                isAllCheck=false;  //当不选中时,全选为false
              }
              //--------新增代码----------end--------
             
              cartList.add(new CartInfoMode.fromJson(item));
    
           });
    
         }
          notifyListeners();
      }

    完成后,到UI界面加入交互效果,打开lib/pages/cart_page/cart_bottom.dart文件,修改selectAllBtn(context)方法。

    //全选按钮
      Widget selectAllBtn(context){
        //--------新增代码----------end--------
        bool isAllCheck = Provide.value<CartProvide>(context).isAllCheck;
        //--------新增代码----------end--------
        return Container(
          child: Row(
            children: <Widget>[
              Checkbox(
                //--------修改代码----------end--------
                value: isAllCheck,
                //--------修改代码----------end--------
                activeColor: Colors.red,
                onChanged: (bool val){},
              ),
              Text('全选')
            ],
          ),
        );
      }

    可以看下效果,加入购物车的商品默认都是选中状态,下面的全选按钮也是选中状态;取消一个上面的选中状态,下面的全选按钮状态也是跟着取消的。

    全选按钮的方法和当个商品很类似,也是在Provide中,新建一个changeAllCheckBtnState方法,写入下面的代码.

    changeAllCheckBtnState(bool isCheck) async{
        SharedPreferences prefs = await SharedPreferences.getInstance(); //初始化
        cartString = prefs.getString('cartInfo'); //得到持久化的字符串
        List<Map> tempList= (json.decode(cartString.toString()) as List).cast(); //声明临时List,用于循环,找到修改项的索引
        List<Map> newList = []; //新建一个List,用于组成新的持久化数据。
        for(var item in tempList){
          var newItem = item; //把老item赋值给新的item 因为Dart不让循环时修改原值
          newItem['isCheck'] = isCheck; //改变选中状态
          newList.add(newItem); //把newItem新的值add到新数组newList里
        }
    
        cartString = json.encode(newList).toString(); //形成字符串
        prefs.setString('cartInfo', cartString); //存储持久化
        await getCartInfo(); //持久化成功后刷新列表
      }

    完成后,到UI界面加入交互效果,打开lib/pages/cart_page/cart_bottom.dart文件,修改selectAllBtn(context)方法。

    //全选按钮
      Widget selectAllBtn(context){
        //--------新增代码----------start--------
        bool isAllCheck = Provide.value<CartProvide>(context).isAllCheck;
        //--------新增代码----------end--------
        return Container(
          child: Row(
            children: <Widget>[
              Checkbox(
                value: isAllCheck,
                activeColor: Colors.pink,
                //--------新增代码----------start--------
                onChanged: (bool val){
                  Provide.value<CartProvide>(context).changeAllCheckBtnState(val);
                },
                //--------新增代码----------end--------
              ),
              Text('全选')
            ],
          ),
        );
      }

    做完这步,就可以测试一下交互效果了。

    11、购物车_商品数量的加减操作

    现在基本购物车页面只差一个商品数量的加减操作了,下面开始。

    编写业务逻辑方法

    直接在lib/provide/cart.dart文件中,新建立一个方法addOrReduceAction()方法。方法接收两个参数.

    • cartItem:要修改的项.
    • todo: 是加还是减。

    代码如下:

    //商品数量的加减
      addOrReduceAction(var cartItem, String todo) async{ 
        SharedPreferences prefs = await SharedPreferences.getInstance(); //初始化
        cartString = prefs.getString('cartInfo'); //得到持久化的字符串
        List<Map> tempList= (json.decode(cartString.toString()) as List).cast(); //声明临时List,用于循环,找到修改项的索引
        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里面是Map,而传递过来的cartItem里是对象
        tempList[changeIndex] = cartItem.toJson(); //cartItem.toJson()变成Map值然后赋值给tempList
        cartString = json.encode(tempList).toString(); //变成字符串
        prefs.setString('cartInfo', cartString); //存储持久化
        await getCartInfo(); //持久化成功后刷新列表
      }

    方法写完后,就可以修改UI部分了,让其有交互效果.

    UI交互效果的修改

    现在页面中引入Provide相关的文件

    import 'package:provide/provide.dart';
    import '../../provide/cart.dart';

    然后设置接收参数,接收item就可以了

    var item;
    CartCount(this.item);

    然后把组件的内部方法都加入参数context,这里直接给出所有代码:

    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    import 'package:provide/provide.dart';
    import '../../provide/cart.dart';
    
    class CartCount extends StatelessWidget {
      //--------------新增加代码------------start--------
      var item;
      CartCount(this.item);
      //--------------新增加代码------------end--------
    
      @override
      Widget build(BuildContext context) {
        return Container(
           ScreenUtil().setWidth(165),
          margin: EdgeInsets.only(top:5.0),
          decoration: BoxDecoration(
            border:Border.all( 1 , color:Colors.black12)
          ),
          child: Row(
            children: <Widget>[
              //--------------新增加代码------------start--------
              _reduceBtn(context),
              _countArea(),
              _addBtn(context),
              //--------------新增加代码------------end--------
            ],
          ),
          
        );
      }
      // 减少按钮
      Widget _reduceBtn(context){
        return InkWell(
          onTap: (){
            //--------------新增加代码------------start--------
            Provide.value<CartProvide>(context).addOrReduceAction(item,'reduce');
            //--------------新增加代码------------end--------
          },
          child: Container(
             ScreenUtil().setWidth(45),
            height: ScreenUtil().setHeight(45),
            alignment: Alignment.center,
           
            decoration: BoxDecoration(
              //--------------新增加代码------------start--------
              color: item.count>1?Colors.white:Colors.black12,
              //--------------新增加代码------------end--------
              border:Border(
                right:BorderSide(1,color:Colors.black12)
              )
            ),
            //--------------新增加代码------------start--------
            child:item.count>1? Text('-'):Text(' '),
            //--------------新增加代码------------end--------
          ),
        );
      }
    
      //添加按钮
      Widget _addBtn(context){
        return InkWell(
          onTap: (){
           //--------------新增加代码------------start--------
            Provide.value<CartProvide>(context).addOrReduceAction(item,'add');
            //--------------新增加代码------------end--------
          },
          child: Container(
             ScreenUtil().setWidth(45),
            height: ScreenUtil().setHeight(45),
            alignment: Alignment.center,
           
             decoration: BoxDecoration(
              color: Colors.white,
              border:Border(
                left:BorderSide(1,color:Colors.black12)
              )
            ),
            child: Text('+'),
          ),
        );
      }
    
      //中间数量显示区域
      Widget _countArea(){
        return Container(
           ScreenUtil().setWidth(70),
          height: ScreenUtil().setHeight(45),
          alignment: Alignment.center,
          color: Colors.white,
          //--------------新增加代码------------start--------
           child: Text('${item.count}'),
          //--------------新增加代码------------end--------
        );
      }
    
    }

    全部改完后,还需要到cart_item.dart里的_cartGoodsName里的调用组件的方法。

    //商品名称
      Widget _cartGoodsName(item){
        return Container(
           ScreenUtil().setWidth(300),
          padding: EdgeInsets.all(10),
          alignment: Alignment.topLeft,
          child: Column(
            children: <Widget>[
              Text(item.goodsName),
              //-----------修改关键代码------start-------
              CartCount(item)
              //-----------修改关键代码------end-------
            ],
          ),
        );
      }

    重新运行后,就应该可以实现商品数量的加减交互了。

    12、购物车_首页Provide化 让跳转随心所欲

    开始学习的时候,底部导航跳转并没有使用Provide,而是使用了简单的变量,这样作的结果就是其它页面没办法控制首页底部导航的跳转,让项目的跳转非常笨拙,缺乏灵活性。这节课就通过我们小小的改造,把首页index_page.dart,加入Provide控制。

    编写Provide文件

    先在lib/provide文件夹下面,新建一个currentIndex.dart文件,然后声明一个索引变量,这个变量就是控制底部导航和页面跳转的。也就是说我们只要把这个索引进行状态管理,那所以的页面可以轻松的控制首页的跳转了。代码如下:

    import 'package:flutter/material.dart';
    
    class CurrentIndexProvide with ChangeNotifier{
      int currentIndex = 0; //当前页面的索引
      
      changeIndex(int newIndex){ //新的索引
        currentIndex = newIndex;
        notifyListeners(); //通知
      }
    
    }

    全局注入

    main.dart页引入

    import './provide/currentIndex.dart';

    下面增加代码:

    var currentIndexProvide = CurrentIndexProvide();
    ...
    ...
    ..provide(Provider<CurrentIndexProvide>.value(currentIndexProvide));

    重新编写首页

    现在就要改造首页了,这次改动的地方比较多,所以干脆先注释掉所有代码,然后重新进行编写。

    修改思路是这样的,把原来的statfulWidget换成静态的statelessWeidget然后进行主要修改build方法里。加入Provide Widget,然后再每次变化时得到索引,点击下边导航时改变索引.

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'home_page.dart';
    import 'category_page.dart';
    import 'cart_page.dart';
    import 'member_page.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    import 'package:provide/provide.dart';
    import '../provide/currentIndex.dart';
    
    class IndexPage extends StatelessWidget {
      final List<BottomNavigationBarItem> bottomTabs = [
        BottomNavigationBarItem(
          icon:Icon(CupertinoIcons.home),
          title:Text('首页')
        ),
        BottomNavigationBarItem(
          icon:Icon(CupertinoIcons.search),
          title:Text('分类')
        ),
        BottomNavigationBarItem(
          icon:Icon(CupertinoIcons.shopping_cart),
          title:Text('购物车')
        ),
         BottomNavigationBarItem(
          icon:Icon(CupertinoIcons.profile_circled),
          title:Text('会员中心')
        ),
      ];
    
       final List<Widget> tabBodies = [
          HomePage(),
          CategoryPage(),
          CartPage(),
          MemberPage()
       ];
    
      @override
      Widget build(BuildContext context) {
       
        ScreenUtil.instance = ScreenUtil( 750, height: 1334)..init(context);  //初始化设计尺寸
      
        return Provide<CurrentIndexProvide>(
    
          builder: (context,child,val){
            //------------关键代码----------start---------
            int currentIndex= Provide.value<CurrentIndexProvide>(context).currentIndex;
            // ----------关键代码-----------end ----------
            return Scaffold(
                backgroundColor: Color.fromRGBO(244, 245, 245, 1.0),
                bottomNavigationBar: BottomNavigationBar(
                  type:BottomNavigationBarType.fixed, //图标+文字
                  currentIndex: currentIndex,
                  items:bottomTabs,
                  onTap: (index){
                    //------------关键代码----------start---------
                    Provide.value<CurrentIndexProvide>(context).changeIndex(index);
                    // ----------关键代码-----------end ----------
                  },
                ),
                 body: IndexedStack( //保持页面
                        index: currentIndex,
                        children: tabBodies
                 ),
            ); 
          }
        );
         
      }
    }

    修改商品详细页,实现跳转

    打开/lib/pages/details_page/details_bottom.dart文件,先引入curretnIndex.dart文件.

    import '../../provide/currentIndex.dart';

    然后修改build方法里的购物车图标区域.在图标的onTap方法里,加入下面的代码.

    InkWell(
      onTap: (){
          //--------------关键代码----------start-----------
          Provide.value<CurrentIndexProvide>(context).changeIndex(2);
          Navigator.pop(context);
          //-------------关键代码-----------end--------
      },
      child: Container(
           ScreenUtil().setWidth(110) ,
          alignment: Alignment.center,
          child:Icon(
                Icons.shopping_cart,
                size: 35,
                color: Colors.red,
              ), 
        ) ,
    ),

    这步做完,可以试着测试一下了,看看是不是可以从详细页直接跳转到购物车页面了。

    13、购物车_详细页显示购物车商品数量

    现在购物车的基本功能都已经做完了,但是商品详细页面还有一个小功能没有完成,就是在商品详细页添加商品到购物车时,购物车的图标要动态显示出此时购物车的数量。

    修改文件结构

    打开/lib/pages/details_page/details_bottom.dart文件,修改图片区域,增加层叠组件Stack Widget,然后在右上角加入购物车现有商品数量。

    children: <Widget>[
             //关键代码--------------------start--------------
              Stack(
                children: <Widget>[
                  InkWell(
                    onTap: () {
                      Provide.value<CurrentIndexProvide>(context).changeIndex(2); //购物车索引是2
                      Navigator.pop(context); //跳转
                    },
                    child: Container(
                       ScreenUtil().setWidth(110),
                      alignment: Alignment.center,
                      child: Icon(Icons.shopping_cart,size: 35,color: Colors.red,),
                    ),
                  ),
                  
                  Provide<CartProvide>(
                    builder: (context,child,val){
                      int goodsCount = Provide.value<CartProvide>(context).allGoodsCount; //获取总数量
                      return Positioned(
                        top:0,
                        right:5,
                        child: Container(
                          padding:EdgeInsets.fromLTRB(6, 3, 6, 3),
                          decoration: BoxDecoration(
                            color:Colors.red,
                            border: Border.all( 2,color: Colors.white),
                            borderRadius: BorderRadius.circular(12.0)
                          ),
                          child: Text(
                            '${goodsCount}',
                            style: TextStyle(fontSize:ScreenUtil().setSp(22),color: Colors.white),
                          ),
                        ),
                      );
                    },
                  )
    
                ],
              ),
              //关键代码--------------------end----------------
              InkWell(   
              ......
            

    重新运行可以看到详情页底部购物车图标的右上角有个总数量了,点击购物车图标跳转到购物车页面。不过‘加入购物车’还没有反应,需要修改cart.dart页面。

    修改provide/cart.dart文件

    因为我们要实现动态展示,所以在添加购物车商品时,应该也有数量的变化,所以需要修改cart.dart文件里的save()方法。

    save(goodsId, goodsName, count, price, images) async{
        SharedPreferences prefs = await SharedPreferences.getInstance(); //初始化
        cartString = prefs.getString('cartInfo'); //获取持久化存储的值
        //判断cartString是否为空,为空说明是第一次添加,或者被key被清除了。
        //如果有值进行decode操作
        var temp = cartString == null ? [] : json.decode(cartString.toString());
        //把获得值转变成List
        List<Map> tempList = (temp as List).cast(); //temp转化为List
        //声明变量,用于判断购物车中是否已经存在此商品ID
        bool isHave = false; //默认为没有
        int ival = 0; //用于进行循环的索引使用
        allPrice = 0; //总价格初始化为0
        allGoodsCount = 0; //把商品总数量初始化为0
        tempList.forEach((item){ //进行循环,找出是否已经存在该商品
          //如果存在,数量进行+1操作
          if(item['goodsId'] == goodsId){
            tempList[ival] ['count'] = item['count'] +1;
            cartList[ival].count++;
            isHave = true;
          }
          //点击为真时,总价格和总数更新
          if(item['isCheck']){
            allPrice += (cartList[ival].price * cartList[ival].count); //总价格=(单价*数量)+(单价*数量)+...
            allGoodsCount += cartList[ival].count;
          }
    
          ival++;
        });
        //如果没有,进行增加
        if(!isHave){
          Map<String, dynamic> newGoods = {
            'goodsId':goodsId,
            'goodsName':goodsName,
            'count':count,
            'price':price,
            'images':images,
            'isCheck':true,  //是否已经选择
          };
          tempList.add(newGoods);
          cartList.add(CartInfoModel.fromJson(newGoods)); //fromJson变成对象
          //没有时,总价格和总数同样更新
          allPrice += (price * count); //总价格=(单价*数量)+(单价*数量)+...
          allGoodsCount += count;
        }
        //把字符串进行encode操作
        cartString = json.encode(tempList).toString(); //json数据转字符串
        // print('字符串>>>>>>>>>>>${cartString}');
        // print('数据模型>>>>>>>>>>>${cartList}');
        prefs.setString('cartInfo', cartString); //进行持久化
        notifyListeners(); //通知
      }

    完成后,就可以实现商品详细页购物车中商品数量的动态展示了。也算我们购物车区域所有功能都已经完成了。

    购物车页面返回商品页

    一般商城在购物车页面,点击商品,还可以跳回到商品页面,下面添加下这个小功能。

    修改cart_item.dart文件,先引入路由:

    import '../../routers/application.dart';

    然后修改商品图片代码:

    //商品图片
      Widget _cartImage(context,item){
        //-----------------关键代码---------start---------
        return InkWell(
          onTap: (){
            Application.router.navigateTo(context, '/detail?id=${item.goodsId}');
          },
          child: Container(
             ScreenUtil().setWidth(150),
            padding: EdgeInsets.all(3.0),
            decoration: BoxDecoration(
              border: Border.all( 1,color: Colors.black12)
            ),
            child: Image.network(item.images),
          )
        );
        //-----------------关键代码---------end---------
      }

    同时上面build也要加上contenx

    _cartImage(context,item),

    重新运行,进入购物车页面,点击商品图片,可以调回到商品详情页了。。。

  • 相关阅读:
    重定向丶管道丶参数传递
    zabbix监控redis
    zabbix监控mysql
    playbook
    zabbix通过jvm监控tomcat
    zabbix监控tcp状态
    部署centos6
    自动选择profile
    java jvm学习笔记十二(访问控制器的栈校验机制)
    java jvm学习笔记十一(访问控制器)
  • 原文地址:https://www.cnblogs.com/joe235/p/11383237.html
Copyright © 2020-2023  润新知