开始制作购物车部分的内容了。这也算是最复杂的一个部分,也是我们基本掌握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( ); } }
然后开始写基本结构,我们这里使用Container
和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( 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),
重新运行,进入购物车页面,点击商品图片,可以调回到商品详情页了。。。