购物车是IT电商项目必不可少的功能之一,尽管该功能不算很复杂,但是却很重要。实现的方式也多种多样,就我而言就曾经通过三种方式开发过购物车:第一种方式是通过LinearLayout嵌套LinearLayout实现,这种方式会消耗更多的内存,数据过多时会造成页面的卡顿,还可能产生OOM异常,不可取;第二种方式是通过ListView嵌套LinearLayout实现,这种方式与第一种大同小异,提升效率有限;第三种方式(也是目前我认为的最佳方式)是通过ExpandableListView实现购物车分店铺功能,由于
ExpandableListView是系统原生控件,因此由系统底层维护并提供了更多的方法供我们使用,操作简单、页面流畅而且代码量较前两种也小了很多。
虽然第一、二中方式现在看起来很不友好,但当时也是我逐步摸索的实践过程,对第三种方式的完成提供了必不可少的贡献。当然,购物车的实现方式多种多样,应该还有更好的实现方式,这篇博客就当作抛砖引玉了,希望大家前来学习交流、批评指正、共同成长。
注:文章末尾附项目源码下载链接。
页面布局文件
Android开发,页面的美观、流畅与用户体验及其重要,因此开发购物车功能的第一步就是布局文件。购物车的主要功能包括:单选、全选、合计、删除、商品数量的加减,为了方便测试又新增了刷新数据功能,先看下页面效果(动图)。实际开发中,美观的页面需要UI的设计与我们的开发,这一点很重要!
以下为购物车的页面布局文件代码:该文件中使用的是ExpandableListView原生控件,来实现购物车的分店铺。实际开发中,如果遇到ScrollView嵌套ExpandableListView控件,可以使用自定义控件ExpandableListViewForScrollView
解决ScrollView嵌套ExpandableListView,ExpandableListView显示不全的问题。
注:文章末尾附ExpandableListViewFroScrollView源码。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ededed" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="48dp" android:background="#ffffff" android:orientation="vertical"> <TextView android:id="@+id/tv_titlebar_left" android:layout_width="wrap_content" android:layout_height="match_parent" android:background="@null" android:gravity="center" android:paddingLeft="13dp" android:paddingRight="13dp" android:singleLine="true" android:text="刷新数据" android:textColor="#4c4c4c" android:textSize="15dp" android:visibility="visible" /> <TextView android:id="@+id/tv_titlebar_center" android:layout_width="200dp" android:layout_height="match_parent" android:layout_centerHorizontal="true" android:ellipsize="end" android:gravity="center" android:maxLength="18" android:singleLine="true" android:text="购物车" android:textColor="#1a1a1a" android:textSize="17dp" android:visibility="visible" /> <TextView android:id="@+id/tv_titlebar_right" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentRight="true" android:background="@null" android:gravity="center" android:paddingLeft="13dp" android:paddingRight="13dp" android:singleLine="true" android:text="编辑" android:textColor="#4c4c4c" android:textSize="15dp" android:visibility="gone" /> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_alignParentBottom="true" android:background="#cccccc" /> </RelativeLayout> <ExpandableListView android:id="@+id/elv_shopping_car" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#ededed" android:divider="@null" android:groupIndicator="@null" android:scrollbars="none" android:visibility="gone" /> <RelativeLayout android:id="@+id/rl" android:layout_width="match_parent" android:layout_height="45dp" android:background="#ffffff" android:visibility="gone"> <LinearLayout android:id="@+id/ll_select_all" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" android:paddingRight="10dp"> <ImageView android:id="@+id/iv_select_all" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="10dp" android:background="@mipmap/unselect" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="6dp" android:text="全选" android:textColor="#333333" android:textSize="14dp" /> </LinearLayout> <Button android:id="@+id/btn_order" android:layout_width="125dp" android:layout_height="match_parent" android:layout_alignParentRight="true" android:background="#ee1d23" android:text="去结算" android:textColor="#ffffff" android:textSize="16dp" android:visibility="visible" /> <Button android:id="@+id/btn_delete" android:layout_width="125dp" android:layout_height="match_parent" android:layout_alignParentRight="true" android:background="#ee1d23" android:text="删除" android:textColor="#ffffff" android:textSize="16dp" android:visibility="gone" /> <RelativeLayout android:id="@+id/rl_total_price" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_toLeftOf="@id/btn_order" android:layout_toRightOf="@id/ll_select_all"> <TextView android:id="@+id/tv_total_price" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginLeft="2dp" android:layout_marginRight="10dp" android:maxLength="12" android:singleLine="true" android:text="¥0.00" android:textColor="#333333" android:textSize="15dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_toLeftOf="@id/tv_total_price" android:text="合计:" android:textColor="#333333" android:textSize="15dp" /> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:background="#cccccc" /> </RelativeLayout> </LinearLayout> <RelativeLayout android:id="@+id/rl_no_contant" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:visibility="gone"> <ImageView android:id="@+id/iv_no_contant" android:layout_width="100dp" android:layout_height="100dp" android:layout_centerHorizontal="true" android:scaleType="centerCrop" android:src="@mipmap/home_tab_no_data" /> <TextView android:layout_width="200dp" android:layout_height="wrap_content" android:layout_below="@+id/iv_no_contant" android:layout_marginTop="20dp" android:gravity="center" android:text="购物车竟然是空的,快去购买书籍充实自己吧!" android:textColor="#808080" android:textSize="16dp" /> </RelativeLayout> </RelativeLayout>
初始化数据(含Bean类)
在实际开发中,通过请求后台接口获取购物车数据并解析(
为了方便测试,在本地模拟了购物车数据),定义了Bean类ShoppingCarDataBean,然后使用Gson按照Bean类的格式解析数据,最后填充到ExpandableListView中使用(详见下方初始化ExpandableListView的数据)。
以下为初始化数据的代码:在此解析数据,并填充到
ExpandableListView中使用。
/** * 初始化数据 */ private void initData() { //使用Gson解析购物车数据, //ShoppingCarDataBean为bean类,Gson按照bean类的格式解析数据 /** * 实际开发中,通过请求后台接口获取购物车数据并解析 */ Gson gson = new Gson(); ShoppingCarDataBean shoppingCarDataBean = gson.fromJson(shoppingCarData, ShoppingCarDataBean.class); datas = shoppingCarDataBean.getDatas(); initExpandableListViewData(datas); }以下为ShoppingCarDataBean的代码:在bean类的DatasBean中新增了isSelect_shop属性,用来判断店铺是否被选中;在GoodsBean中新增了isSelect属性,用来判断商品是否被选中。正是通过这两个属性,完成了单选、全选、删除、合计以及后续的去结算操作,不仅大大的减少了代码量,还精简了业务逻辑、保证了数据的精确。所以,越是复杂的功能,Bean类的作用也就越重要,大家不要忽略它的作用。
package com.wangyang.shoppingcarbestimplementation.bean; import java.util.List; /** * 购物车数据的bean类 */ public class ShoppingCarDataBean { private int code; private List<DatasBean> datas; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public List<DatasBean> getDatas() { return datas; } public void setDatas(List<DatasBean> datas) { this.datas = datas; } public static class DatasBean { private String store_id; private String store_name; private boolean isSelect_shop; //店铺是否在购物车中被选中 private List<GoodsBean> goods; public boolean getIsSelect_shop() { return isSelect_shop; } public void setIsSelect_shop(boolean select_shop) { isSelect_shop = select_shop; } public String getStore_id() { return store_id; } public void setStore_id(String store_id) { this.store_id = store_id; } public String getStore_name() { return store_name; } public void setStore_name(String store_name) { this.store_name = store_name; } public List<GoodsBean> getGoods() { return goods; } public void setGoods(List<GoodsBean> goods) { this.goods = goods; } public static class GoodsBean { private String goods_id; private String goods_image; private String goods_name; private String goods_num; private String goods_price; private boolean isSelect; //商品是否在购物车中被选中 public boolean getIsSelect() { return isSelect; } public void setIsSelect(boolean isSelect) { this.isSelect = isSelect; } public String getGoods_id() { return goods_id; } public void setGoods_id(String goods_id) { this.goods_id = goods_id; } public String getGoods_image() { return goods_image; } public void setGoods_image(String goods_image) { this.goods_image = goods_image; } public String getGoods_name() { return goods_name; } public void setGoods_name(String goods_name) { this.goods_name = goods_name; } public String getGoods_num() { return goods_num; } public void setGoods_num(String goods_num) { this.goods_num = goods_num; } public String getGoods_price() { return goods_price; } public void setGoods_price(String goods_price) { this.goods_price = goods_price; } } } }
初始化ExpandableListView
页面布局开发完成后,接下来就需要通过代码来实现业务逻辑了,首先就需要初始化
ExpandableListView控件,为其创建数据适配器并进行初始化操作。因为使用的是ExpandableListView,所以adapter继承BaseExpandableListAdapter,并实现其未实现的方法。
以下为初始化ExpandableListView的代码:在此对删除与修改商品数量的回调进行了处理。
/** * 初始化ExpandableListView * 创建数据适配器adapter,并进行初始化操作 */ private void initExpandableListView() { shoppingCarAdapter = new ShoppingCarAdapter(context, llSelectAll, ivSelectAll, btnOrder, btnDelete, rlTotalPrice, tvTotalPrice); elvShoppingCar.setAdapter(shoppingCarAdapter); //删除的回调 shoppingCarAdapter.setOnDeleteListener(new ShoppingCarAdapter.OnDeleteListener() { @Override public void onDelete() { initDelete(); /** * 实际开发中,在此请求删除接口,删除成功后, * 通过initExpandableListViewData()方法刷新购物车数据。 * 注:通过bean类中的DatasBean的isSelect_shop属性,判断店铺是否被选中; * GoodsBean的isSelect属性,判断商品是否被选中, * (true为选中,false为未选中) */ } }); //修改商品数量的回调 shoppingCarAdapter.setOnChangeCountListener(new ShoppingCarAdapter.OnChangeCountListener() { @Override public void onChangeCount(String goods_id) { /** * 实际开发中,在此请求修改商品数量的接口,商品数量修改成功后, * 通过initExpandableListViewData()方法刷新购物车数据。 */ } }); }以下为购物车的数据适配器ShoppingCarAdapter的代码:在此对单选、全选、合计、商品数量加减(本地)以及数据的刷新进行了处理,并定义了删除、商品数量加减回调接口。代码中已经给出了较为详细的注释,在这里就不再分段解释了,如果有不明白的地方,评论联系我即可。
package com.wangyang.shoppingcarbestimplementation.adapter; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import com.bumptech.glide.Glide; import com.wangyang.shoppingcarbestimplementation.R; import com.wangyang.shoppingcarbestimplementation.bean.ShoppingCarDataBean; import com.wangyang.shoppingcarbestimplementation.util.ToastUtil; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import butterknife.ButterKnife; import butterknife.InjectView; /** * 购物车的adapter * 因为使用的是ExpandableListView,所以继承BaseExpandableListAdapter */ public class ShoppingCarAdapter extends BaseExpandableListAdapter { private final Context context; private final LinearLayout llSelectAll; private final ImageView ivSelectAll; private final Button btnOrder; private final Button btnDelete; private final RelativeLayout rlTotalPrice; private final TextView tvTotalPrice; private List<ShoppingCarDataBean.DatasBean> data; private boolean isSelectAll = false; private double total_price; public ShoppingCarAdapter(Context context, LinearLayout llSelectAll, ImageView ivSelectAll, Button btnOrder, Button btnDelete, RelativeLayout rlTotalPrice, TextView tvTotalPrice) { this.context = context; this.llSelectAll = llSelectAll; this.ivSelectAll = ivSelectAll; this.btnOrder = btnOrder; this.btnDelete = btnDelete; this.rlTotalPrice = rlTotalPrice; this.tvTotalPrice = tvTotalPrice; } /** * 自定义设置数据方法; * 通过notifyDataSetChanged()刷新数据,可保持当前位置 * * @param data 需要刷新的数据 */ public void setData(List<ShoppingCarDataBean.DatasBean> data) { this.data = data; notifyDataSetChanged(); } @Override public int getGroupCount() { if (data != null && data.size() > 0) { return data.size(); } else { return 0; } } @Override public Object getGroup(int groupPosition) { return data.get(groupPosition); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public View getGroupView(final int groupPosition, final boolean isExpanded, View convertView, ViewGroup parent) { GroupViewHolder groupViewHolder; if (convertView == null) { convertView = View.inflate(context, R.layout.item_shopping_car_group, null); groupViewHolder = new GroupViewHolder(convertView); convertView.setTag(groupViewHolder); } else { groupViewHolder = (GroupViewHolder) convertView.getTag(); } final ShoppingCarDataBean.DatasBean datasBean = data.get(groupPosition); //店铺ID String store_id = datasBean.getStore_id(); //店铺名称 String store_name = datasBean.getStore_name(); if (store_name != null) { groupViewHolder.tvStoreName.setText(store_name); } else { groupViewHolder.tvStoreName.setText(""); } //店铺内的商品都选中的时候,店铺的也要选中 for (int i = 0; i < datasBean.getGoods().size(); i++) { ShoppingCarDataBean.DatasBean.GoodsBean goodsBean = datasBean.getGoods().get(i); boolean isSelect = goodsBean.getIsSelect(); if (isSelect) { datasBean.setIsSelect_shop(true); } else { datasBean.setIsSelect_shop(false); break; } } //因为set之后要重新get,所以这一块代码要放到一起执行 //店铺是否在购物车中被选中 final boolean isSelect_shop = datasBean.getIsSelect_shop(); if (isSelect_shop) { groupViewHolder.ivSelect.setImageResource(R.mipmap.select); } else { groupViewHolder.ivSelect.setImageResource(R.mipmap.unselect); } //店铺选择框的点击事件 groupViewHolder.ll.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { datasBean.setIsSelect_shop(!isSelect_shop); List<ShoppingCarDataBean.DatasBean.GoodsBean> goods = datasBean.getGoods(); for (int i = 0; i < goods.size(); i++) { ShoppingCarDataBean.DatasBean.GoodsBean goodsBean = goods.get(i); goodsBean.setIsSelect(!isSelect_shop); } notifyDataSetChanged(); } }); //当所有的选择框都是选中的时候,全选也要选中 w: for (int i = 0; i < data.size(); i++) { List<ShoppingCarDataBean.DatasBean.GoodsBean> goods = data.get(i).getGoods(); for (int y = 0; y < goods.size(); y++) { ShoppingCarDataBean.DatasBean.GoodsBean goodsBean = goods.get(y); boolean isSelect = goodsBean.getIsSelect(); if (isSelect) { isSelectAll = true; } else { isSelectAll = false; break w;//根据标记,跳出嵌套循环 } } } if (isSelectAll) { ivSelectAll.setBackgroundResource(R.mipmap.select); } else { ivSelectAll.setBackgroundResource(R.mipmap.unselect); } //全选的点击事件 llSelectAll.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { isSelectAll = !isSelectAll; if (isSelectAll) { for (int i = 0; i < data.size(); i++) { List<ShoppingCarDataBean.DatasBean.GoodsBean> goods = data.get(i).getGoods(); for (int y = 0; y < goods.size(); y++) { ShoppingCarDataBean.DatasBean.GoodsBean goodsBean = goods.get(y); goodsBean.setIsSelect(true); } } } else { for (int i = 0; i < data.size(); i++) { List<ShoppingCarDataBean.DatasBean.GoodsBean> goods = data.get(i).getGoods(); for (int y = 0; y < goods.size(); y++) { ShoppingCarDataBean.DatasBean.GoodsBean goodsBean = goods.get(y); goodsBean.setIsSelect(false); } } } notifyDataSetChanged(); } }); //合计的计算 total_price = 0.0; tvTotalPrice.setText("¥0.00"); for (int i = 0; i < data.size(); i++) { List<ShoppingCarDataBean.DatasBean.GoodsBean> goods = data.get(i).getGoods(); for (int y = 0; y < goods.size(); y++) { ShoppingCarDataBean.DatasBean.GoodsBean goodsBean = goods.get(y); boolean isSelect = goodsBean.getIsSelect(); if (isSelect) { String num = goodsBean.getGoods_num(); String price = goodsBean.getGoods_price(); double v = Double.parseDouble(num); double v1 = Double.parseDouble(price); total_price = total_price + v * v1; //让Double类型完整显示,不用科学计数法显示大写字母E DecimalFormat decimalFormat = new DecimalFormat("0.00"); tvTotalPrice.setText("¥" + decimalFormat.format(total_price)); } } } //去结算的点击事件 btnOrder.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //创建临时的List,用于存储被选中的商品 List<ShoppingCarDataBean.DatasBean.GoodsBean> temp = new ArrayList<>(); for (int i = 0; i < data.size(); i++) { List<ShoppingCarDataBean.DatasBean.GoodsBean> goods = data.get(i).getGoods(); for (int y = 0; y < goods.size(); y++) { ShoppingCarDataBean.DatasBean.GoodsBean goodsBean = goods.get(y); boolean isSelect = goodsBean.getIsSelect(); if (isSelect) { temp.add(goodsBean); } } } if (temp != null && temp.size() > 0) {//如果有被选中的 /** * 实际开发中,如果有被选中的商品, * 则跳转到确认订单页面,完成后续订单流程。 */ ToastUtil.makeText(context, "跳转到确认订单页面,完成后续订单流程"); } else { ToastUtil.makeText(context, "请选择要购买的商品"); } } }); //删除的点击事件 btnDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /** * 实际开发中,通过回调请求后台接口实现删除操作 */ if (mDeleteListener != null) { mDeleteListener.onDelete(); } } }); return convertView; } static class GroupViewHolder { @InjectView(R.id.iv_select) ImageView ivSelect; @InjectView(R.id.tv_store_name) TextView tvStoreName; @InjectView(R.id.ll) LinearLayout ll; GroupViewHolder(View view) { ButterKnife.inject(this, view); } } //------------------------------------------------------------------------------------------------ @Override public int getChildrenCount(int groupPosition) { if (data.get(groupPosition).getGoods() != null && data.get(groupPosition).getGoods().size() > 0) { return data.get(groupPosition).getGoods().size(); } else { return 0; } } @Override public Object getChild(int groupPosition, int childPosition) { return data.get(groupPosition).getGoods().get(childPosition); } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { ChildViewHolder childViewHolder; if (convertView == null) { convertView = View.inflate(context, R.layout.item_shopping_car_child, null); childViewHolder = new ChildViewHolder(convertView); convertView.setTag(childViewHolder); } else { childViewHolder = (ChildViewHolder) convertView.getTag(); } final ShoppingCarDataBean.DatasBean datasBean = data.get(groupPosition); //店铺ID String store_id = datasBean.getStore_id(); //店铺名称 String store_name = datasBean.getStore_name(); //店铺是否在购物车中被选中 final boolean isSelect_shop = datasBean.getIsSelect_shop(); final ShoppingCarDataBean.DatasBean.GoodsBean goodsBean = datasBean.getGoods().get(childPosition); //商品图片 String goods_image = goodsBean.getGoods_image(); //商品ID final String goods_id = goodsBean.getGoods_id(); //商品名称 String goods_name = goodsBean.getGoods_name(); //商品价格 String goods_price = goodsBean.getGoods_price(); //商品数量 String goods_num = goodsBean.getGoods_num(); //商品是否被选中 final boolean isSelect = goodsBean.getIsSelect(); Glide.with(context) .load(goods_image) .into(childViewHolder.ivPhoto); if (goods_name != null) { childViewHolder.tvName.setText(goods_name); } else { childViewHolder.tvName.setText(""); } if (goods_price != null) { childViewHolder.tvPriceValue.setText(goods_price); } else { childViewHolder.tvPriceValue.setText(""); } if (goods_num != null) { childViewHolder.tvEditBuyNumber.setText(goods_num); } else { childViewHolder.tvEditBuyNumber.setText(""); } //商品是否被选中 if (isSelect) { childViewHolder.ivSelect.setImageResource(R.mipmap.select); } else { childViewHolder.ivSelect.setImageResource(R.mipmap.unselect); } //商品选择框的点击事件 childViewHolder.ivSelect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { goodsBean.setIsSelect(!isSelect); if (!isSelect == false) { datasBean.setIsSelect_shop(false); } notifyDataSetChanged(); } }); //加号的点击事件 childViewHolder.ivEditAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //模拟加号操作 String num = goodsBean.getGoods_num(); Integer integer = Integer.valueOf(num); integer++; goodsBean.setGoods_num(integer + ""); notifyDataSetChanged(); /** * 实际开发中,通过回调请求后台接口实现数量的加减 */ if (mChangeCountListener != null) { mChangeCountListener.onChangeCount(goods_id); } } }); //减号的点击事件 childViewHolder.ivEditSubtract.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //模拟减号操作 String num = goodsBean.getGoods_num(); Integer integer = Integer.valueOf(num); if (integer > 1) { integer--; goodsBean.setGoods_num(integer + ""); /** * 实际开发中,通过回调请求后台接口实现数量的加减 */ if (mChangeCountListener != null) { mChangeCountListener.onChangeCount(goods_id); } } else { ToastUtil.makeText(context, "商品不能再减少了"); } notifyDataSetChanged(); } }); if (childPosition == data.get(groupPosition).getGoods().size() - 1) { childViewHolder.view.setVisibility(View.GONE); childViewHolder.viewLast.setVisibility(View.VISIBLE); } else { childViewHolder.view.setVisibility(View.VISIBLE); childViewHolder.viewLast.setVisibility(View.GONE); } return convertView; } static class ChildViewHolder { @InjectView(R.id.iv_select) ImageView ivSelect; @InjectView(R.id.iv_photo) ImageView ivPhoto; @InjectView(R.id.tv_name) TextView tvName; @InjectView(R.id.tv_price_key) TextView tvPriceKey; @InjectView(R.id.tv_price_value) TextView tvPriceValue; @InjectView(R.id.iv_edit_subtract) ImageView ivEditSubtract; @InjectView(R.id.tv_edit_buy_number) TextView tvEditBuyNumber; @InjectView(R.id.iv_edit_add) ImageView ivEditAdd; @InjectView(R.id.view) View view; @InjectView(R.id.view_last) View viewLast; ChildViewHolder(View view) { ButterKnife.inject(this, view); } } //----------------------------------------------------------------------------------------------- @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return false; } @Override public boolean hasStableIds() { return false; } //删除的回调 public interface OnDeleteListener { void onDelete(); } public void setOnDeleteListener(OnDeleteListener listener) { mDeleteListener = listener; } private OnDeleteListener mDeleteListener; //修改商品数量的回调 public interface OnChangeCountListener { void onChangeCount(String goods_id); } public void setOnChangeCountListener(OnChangeCountListener listener) { mChangeCountListener = listener; } private OnChangeCountListener mChangeCountListener; }
初始化ExpandableListView的数据
数据和ExpandableListView的初始化操作完成后,接下来就是将数据填充到ExpandableListView中使用。同时在数据刷新的时候,通过此方法不仅可以刷新页面数据,还能保证页面保持在当前位置,提升用户体验。
以下为初始化ExpandableListView数据的代码:在此初始化了ExpandableListView的数据,并保证数据刷新时,页面保持当前位置。同时也使ExpandableListView的所有组展开,并点击无效果。
/** * 初始化ExpandableListView的数据 * 并在数据刷新时,页面保持当前位置 * * @param datas 购物车的数据 */ private void initExpandableListViewData(List<ShoppingCarDataBean.DatasBean> datas) { if (datas != null && datas.size() > 0) { //刷新数据时,保持当前位置 shoppingCarAdapter.setData(datas); //使所有组展开 for (int i = 0; i < shoppingCarAdapter.getGroupCount(); i++) { elvShoppingCar.expandGroup(i); } //使组点击无效果 elvShoppingCar.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { return true; } }); tvTitlebarRight.setVisibility(View.VISIBLE); tvTitlebarRight.setText("编辑"); rlNoContant.setVisibility(View.GONE); elvShoppingCar.setVisibility(View.VISIBLE); rl.setVisibility(View.VISIBLE); rlTotalPrice.setVisibility(View.VISIBLE); btnOrder.setVisibility(View.VISIBLE); btnDelete.setVisibility(View.GONE); } else { tvTitlebarRight.setVisibility(View.GONE); rlNoContant.setVisibility(View.VISIBLE); elvShoppingCar.setVisibility(View.GONE); rl.setVisibility(View.GONE); } }
小结
以上就是购物车的主要功能,另外本地删除功能、圆角删除dialog以及商品数量的加减在源码中也有详细的注释,在这里就不一一赘述了,实际开发中还是需要通过接口完成删除与商品数量的加减等功能,保持前后台数据的及时性与一致性。由于图片加载使用了Glide框架,所以需要在AndroidManifest清单文件中添加INTERNET访问网络的权限。
Android Studio已经发布到3.0以上了,但是本项目时间较老了,使用的gradle还是2.2,版本不同的需要修改下。也可以将java文件、资源文件copy到新创建的项目中使用,都很方便。