购物车逻辑,对实际开发中简单而言,主要细节(当然难度谈不上)在于处理逻辑:

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);
    }
}

最后实现的效果图

android电商购物车 android购物车实现_android电商购物车



实现方式二(两层布局的嵌套)

关于思想,我们将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);
    }
}