购物车逻辑,对实际开发中简单而言,主要细节(当然难度谈不上)在于处理逻辑:
A,点击购物结算时的全选触发所有shop及其goods的全选与数据处理;
B,同时,点击shop全选勾选其goods的全选与数据处理;
C,反过来,点击对应店铺里的goods,如果全部勾选了该店铺所有goods触发shop的勾选。同时购物车里所有的goods都勾选了,则由goods或者shop触发购物车的全选及数据处理。
实现方式一(单层布局的实现)
关于思想,我们将shop与goods的item画在一个xml里面,显示的时候分为两种显示:第一种位于shop下面的numberOne goods,它显示shop布局+goods布局;第二种不是这种类型的我们将商铺布局set Gone,它显示goods布局。然后还有不明白的可以接着看,文末有demo下载地址。
这里以RecycleView为例子简单叙述一下实现步骤:
1.1,首先我们得画一个RecycleView的布局,即购物车的整体界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:orientation="vertical"
tools:context="com.xg.xgshopcartdemo.MainActivity">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:id="@+id/recycleview"></android.support.v7.widget.RecyclerView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0.8"
android:id="@+id/ll_selectAll"
android:layout_marginLeft="20dp">
<ImageView
android:layout_width="30dp"
android:layout_height="match_parent"
android:id="@+id/iv_selectAll"
android:src="@drawable/shopcart_selected"/>
<TextView
android:layout_width="50dp"
android:layout_height="match_parent"
android:gravity="center"
android:id="@+id/tv_selectAll"
android:text="全选"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:layout_weight="1"
android:id="@+id/tv_allPrice"
android:textColor="@android:color/holo_red_dark"
android:text="总价:17.0元"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top"
android:id="@+id/tv_allGoodsNum"
android:layout_weight="1"
android:text="共4件商品"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0.9">
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:id="@+id/btn_commit"
android:textColor="@android:color/holo_red_dark"
android:text="去结算"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
1.2,其次我们还得画一个布局,即里面Item的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--shop-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:id="@+id/ll_shopcart_header"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/shopcart_selected"
android:id="@+id/iv_shopSelect"
android:layout_marginLeft="20dp"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/iv_shopImag"
android:src="@drawable/shopcart_entershop"
android:layout_marginLeft="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:id="@+id/tv_shopName"
android:text="XX店铺"/>
</LinearLayout>
<!--goods-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"
android:layout_marginLeft="50dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/iv_goodsSelect"
android:src="@drawable/shopcart_selected" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="right"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_marginLeft="70dp"
android:gravity="center_vertical"
android:id="@+id/tv_goodsName"
android:text="商品名称"/>
<TextView
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_marginLeft="70dp"
android:gravity="center_vertical|right"
android:id="@+id/tv_goodsPrice"
android:textColor="@android:color/holo_red_dark"
android:text="¥1.0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_marginLeft="70dp"
android:id="@+id/iv_goodsImage"
android:src="@drawable/shopcart_entershop"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="30dp"
android:gravity="center_vertical"
android:id="@+id/tv_goodsDescription"
android:text="颜色:蓝色 尺寸:大"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:id="@+id/iv_mine"
android:src="@drawable/shopcart_minus_red"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:id="@+id/tv_goodsNum"
android:text="11"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:id="@+id/iv_goodsAdd"
android:src="@drawable/shopcart_add_red"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="50dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="删 除"
android:id="@+id/tv_delete"
android:layout_marginRight="20dp"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
1.3,这里我们需要开始写Adapter,继承RecyclerView.Adapter<ShopCartAdapter.MyViewHolder>。
常规做法,先初始化id,然后绑定布局,set数据(同时要判断各种状态,包括勾选的显示,shop布局的显示等)和事件的处理。
public class ShopCartAdapter extends RecyclerView.Adapter<ShopCartAdapter.MyViewHolder>{
private Context context;
private List<ShopCartBean.CartlistBean> data;
private OnDeleteClickListener mOnDeleteClickListener;
private OnEditClickListener mOnEditClickListener;
private OnResfreshListener mOnResfreshListener;
private OnItemClickListener mOnItemClickListener;
//构造方法
public ShopCartAdapter(Context context, List<ShopCartBean.CartlistBean> data){
this.context = context;
this.data = data;
}
//布局绑定
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_shopcart, parent, false);
return new MyViewHolder(view);
}
// 数据绑定
@Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
Glide.with(context).load(data.get(position).getDefaultPic()).into(holder.iv_goodsImage);
if (position > 0) {
if (data.get(position).getShopId() == data.get(position - 1).getShopId()) {
//不是第一条数据 && ShopId与上一条数据不一致 -> GONE
holder.ll_shopcart_header.setVisibility(View.GONE);
} else {
holder.ll_shopcart_header.setVisibility(View.VISIBLE);
}
}else {
holder.ll_shopcart_header.setVisibility(View.VISIBLE);
}
//数据填充
holder.tv_goodsDescription.setText("颜色:" + data.get(position).getColor()+" "+"尺寸:" + data.get(position).getSize());
holder.tv_goodsName.setText(data.get(position).getProductName());
holder.tv_shopName.setText(data.get(position).getShopName());
holder.tv_goodsPrice.setText("¥" + data.get(position).getPrice());
holder.tv_goodsNum.setText(data.get(position).getCount() + "");
if(mOnResfreshListener != null){
boolean isSelect = false;
for(int i = 0;i < data.size(); i++){
if(!data.get(i).getIsSelect()){
isSelect = false;
break;//终止当前循环,没有全选
}else{
isSelect = true;
}
}
mOnResfreshListener.onResfresh(isSelect);
}
//给每个item->GoodsMin绑定点击事件,数量的改变
holder.iv_mine.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(data.get(position).getCount() > 1) {
int count = data.get(position).getCount() - 1;
if (mOnEditClickListener != null) {
mOnEditClickListener.onEditClick(position, data.get(position).getId(), count);
}
data.get(position).setCount(count);
notifyDataSetChanged();
}
}
});
//给每个item->GoodsAdd绑定点击事件,数量的改变
holder.iv_goodsAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int count = data.get(position).getCount() + 1;
if(mOnEditClickListener != null){
mOnEditClickListener.onEditClick(position,data.get(position).getId(),count);
}
data.get(position).setCount(count);
notifyDataSetChanged();
}
});
// 商品是否勾选
if(data.get(position).getIsSelect()){
holder.iv_goodsSelect.setImageDrawable(context.getResources().getDrawable(R.drawable.shopcart_selected));
}else {
holder.iv_goodsSelect.setImageDrawable(context.getResources().getDrawable(R.drawable.shopcart_unselected));
}
// 店铺是否勾选
if(data.get(position).getIsShopSelect()){
holder.iv_shopSelect.setImageDrawable(context.getResources().getDrawable(R.drawable.shopcart_selected));
}else {
holder.iv_shopSelect.setImageDrawable(context.getResources().getDrawable(R.drawable.shopcart_unselected));
}
//每个item删除的点击事件
holder.tv_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showDialog(v,position);
}
});
//商品选中的点击事件
holder.iv_goodsSelect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//商铺的选中信息取反
data.get(position).setSelect(!data.get(position).getIsSelect());
//通过循环找出不同商铺的第一个商品的位置
for(int i = 0;i < data.size(); i++){
// IsFirst字段,是否是第一个的标志
if(data.get(i).getIsFirst() == 1) {
//遍历去找出同一家商铺的所有商品的勾选情况
for(int j = 0;j < data.size();j++){
//如果是同一家商铺的商品,并且其中一个商品是未选中,那么商铺的全选勾选取消
if(data.get(j).getShopId() == data.get(i).getShopId() && !data.get(j).getIsSelect()){
data.get(i).setShopSelect(false);
break;
}else{
//如果是同一家商铺的商品,并且所有商品是选中,那么商铺的选中全选勾选
data.get(i).setShopSelect(true);
}
}
}
}
notifyDataSetChanged();
}
});
//shop选中的点击事件
holder.iv_shopSelect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(data.get(position).getIsFirst() == 1) {
// 当前商铺勾选状态取反
data.get(position).setShopSelect(!data.get(position).getIsShopSelect());
for(int i = 0;i < data.size();i++){
// 该商铺下所有商品勾选状态同商铺一样
if(data.get(i).getShopId() == data.get(position).getShopId()){
data.get(i).setSelect(data.get(position).getIsShopSelect());
}
}
notifyDataSetChanged();
}
}
});
}
private void showDialog(final View view, final int position){
//调用删除某个规格商品的接口
if(mOnDeleteClickListener != null){
mOnDeleteClickListener.onDeleteClick(view,position,data.get(position).getId());
}
data.remove(position);
//重新排序,标记所有商品不同商铺第一个的商品位置
isSelectFirst(data);
notifyDataSetChanged();
}
@Override
public int getItemCount() {
int count = (data == null ? 0 : data.size());
return count;
}
class MyViewHolder extends RecyclerView.ViewHolder{
private ImageView iv_shopSelect,iv_shopImag,iv_goodsSelect,iv_goodsImage,iv_mine,iv_goodsAdd;
private TextView tv_shopName,tv_goodsName,tv_goodsDescription,tv_goodsNum,tv_goodsPrice,tv_delete;
private LinearLayout ll_shopcart_header;
public MyViewHolder(View itemView) {
super(itemView);
ll_shopcart_header=itemView.findViewById(R.id.ll_shopcart_header);
iv_shopSelect=itemView.findViewById(R.id.iv_shopSelect);
iv_shopImag=itemView.findViewById(R.id.iv_shopImag);
iv_goodsSelect=itemView.findViewById(R.id.iv_goodsSelect);
iv_goodsImage=itemView.findViewById(R.id.iv_goodsImage);
iv_mine=itemView.findViewById(R.id.iv_mine);
iv_goodsAdd=itemView.findViewById(R.id.iv_goodsAdd);
tv_shopName=itemView.findViewById(R.id.tv_shopName);
tv_goodsName=itemView.findViewById(R.id.tv_goodsName);
tv_goodsDescription=itemView.findViewById(R.id.tv_goodsDescription);
tv_goodsNum=itemView.findViewById(R.id.tv_goodsNum);
tv_goodsPrice=itemView.findViewById(R.id.tv_goodsPrice);
tv_delete=itemView.findViewById(R.id.tv_delete);
}
}
public void isSelectFirst(List<ShopCartBean.CartlistBean> list){
if(list.size() > 0) {
list.get(0).setIsFirst(1);
for (int i = 1; i < list.size(); i++) {
if (list.get(i).getShopId() == list.get(i - 1).getShopId()) {
list.get(i).setIsFirst(2);
} else {
list.get(i).setIsFirst(1);
}
}
}
}
/*********************************事件接口*******************************************************/
// 删除事件初始化
public void setOnDeleteClickListener(OnDeleteClickListener mOnDeleteClickListener){
this.mOnDeleteClickListener = mOnDeleteClickListener;
}
// 刷新事件初始化
public void setResfreshListener(OnResfreshListener mOnResfreshListener){
this.mOnResfreshListener = mOnResfreshListener;
}
// 数量改变监听事件初始化
public void setOnEditClickListener(OnEditClickListener mOnEditClickListener){
this.mOnEditClickListener = mOnEditClickListener;
}
// item的点击事件初始化
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener){
this.mOnItemClickListener = mOnItemClickListener;
}
}
值得注意的是:我们这里直接强制改变了后台传过来的bean文件,加入了是否勾选等我们自己需要的字段。这样不太合理,应该改为new出我们需要的字段数组另外保存,这样较为妥善。
1.4,然后就是在购物车MainActivity中处理各种获取的数据,包括删除商品数量、修改商品数量、商品的勾选、商铺的勾选、购物车的勾选等所拿到的数据
public class MainActivity extends AppCompatActivity {
private LinearLayout ll_selectAll;
private Button btn_commit;
private TextView tv_allPrice,tv_allGoodsNum,tv_selectAll;
private RecyclerView recycleview;
private ImageView iv_selectAll;
private List<ShopCartBean.CartlistBean> mAllOrderList = new ArrayList<>();
private ArrayList<ShopCartBean.CartlistBean> mGoPayList = new ArrayList<>();
private ShopCartAdapter mShopCartAdapter;
private int mCount,mPosition;
private boolean mSelect;
private float mTotalPrice1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ll_selectAll= (LinearLayout) findViewById(R.id.ll_selectAll);
btn_commit= (Button) findViewById(R.id.btn_commit);
tv_allPrice= (TextView) findViewById(R.id.tv_allPrice);
tv_allGoodsNum= (TextView) findViewById(R.id.tv_allGoodsNum);
tv_selectAll= (TextView) findViewById(R.id.tv_selectAll);
iv_selectAll= (ImageView) findViewById(R.id.iv_selectAll);
recycleview= (RecyclerView) findViewById(R.id.recycleview);
recycleview.setLayoutManager(new LinearLayoutManager(this));
mShopCartAdapter = new ShopCartAdapter(this,mAllOrderList);
recycleview.setAdapter(mShopCartAdapter);
//删除商品接口
mShopCartAdapter.setOnDeleteClickListener(new OnDeleteClickListener() {
@Override
public void onDeleteClick(View view, int position, int cartid) {
mShopCartAdapter.notifyDataSetChanged();
}
});
//修改数量接口
mShopCartAdapter.setOnEditClickListener(new OnEditClickListener() {
@Override
public void onEditClick(int position, int cartid, int count) {
mCount = count;
mPosition = position;
}
});
//实时监控全选按钮
mShopCartAdapter.setResfreshListener(new OnResfreshListener() {
@Override
public void onResfresh( boolean isSelect) {
mSelect = isSelect;
if(isSelect){
iv_selectAll.setImageDrawable(getResources().getDrawable(R.drawable.shopcart_selected));
}else {
iv_selectAll.setImageDrawable(getResources().getDrawable(R.drawable.shopcart_unselected));
}
float mTotalPrice = 0;
int mTotalNum = 0;
mTotalPrice1 = 0;
mGoPayList.clear();
for(int i = 0;i < mAllOrderList.size(); i++)
if(mAllOrderList.get(i).getIsSelect()) {
mTotalPrice += Float.parseFloat(mAllOrderList.get(i).getPrice()) * mAllOrderList.get(i).getCount();
mTotalNum += 1;
mGoPayList.add(mAllOrderList.get(i));
}
mTotalPrice1 = mTotalPrice;
tv_allPrice.setText("总价:" + mTotalPrice);
tv_allGoodsNum.setText("共" + mTotalNum + "件商品");
}
});
//全选
ll_selectAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSelect = !mSelect;
if(mSelect){
iv_selectAll.setImageDrawable(getResources().getDrawable(R.drawable.shopcart_selected));
for(int i = 0;i < mAllOrderList.size();i++){
mAllOrderList.get(i).setSelect(true);
mAllOrderList.get(i).setShopSelect(true);
}
}else{
iv_selectAll.setImageDrawable(getResources().getDrawable(R.drawable.shopcart_unselected));
for(int i = 0;i < mAllOrderList.size();i++){
mAllOrderList.get(i).setSelect(false);
mAllOrderList.get(i).setShopSelect(false);
}
}
mShopCartAdapter.notifyDataSetChanged();
}
});
initData();
mShopCartAdapter.notifyDataSetChanged();
}
private void initData(){
for(int i = 0;i < 2;i ++){
ShopCartBean.CartlistBean sb = new ShopCartBean.CartlistBean();
sb.setShopId(1);
sb.setPrice("1.0");
sb.setDefaultPic("http://img2.3lian.com/2014/c7/25/d/40.jpg");
sb.setProductName("狼牙龙珠鼠标");
sb.setShopName("狼牙龙珠");
sb.setColor("蓝色");
sb.setCount(2);
mAllOrderList.add(sb);
}
for(int i = 0;i < 2;i ++){
ShopCartBean.CartlistBean sb = new ShopCartBean.CartlistBean();
sb.setShopId(2);
sb.setPrice("1.0");
sb.setDefaultPic("http://img2.3lian.com/2014/c7/25/d/40.jpg");
sb.setProductName("达尔优鼠标");
sb.setShopName("达尔优");
sb.setColor("绿色");
sb.setCount(2);
mAllOrderList.add(sb);
}
mShopCartAdapter.isSelectFirst(mAllOrderList);
}
}
最后实现的效果图
实现方式二(两层布局的嵌套)
关于思想,我们将shop放在最外层listview中,然后将每个shop对应的goods放在子listview中,让shop来管理goods。这样我们需要在shop中绘制xml shopHeader+goodsListView。
关于点击子listview我们无法获取父listview的position的处理办法:在给子listview setAdapter的时候我们将它所在的父listview的parentPosition传过去,这样我们在点击最里面item的时候就相当于拿到了parentPosition与position两个。
第二层listview我们需要自定义重写onMeassure方法
public class MyListView extends ListView{
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec=MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}