1. 程式人生 > >仿京東中購物車列表模組的實現【以及通過Builder的方式建立dialog彈窗 鏈式呼叫】

仿京東中購物車列表模組的實現【以及通過Builder的方式建立dialog彈窗 鏈式呼叫】

前段時間見群裡有個小夥伴,發了一張電商專案中比較常見的購物車列表的效果圖,問這樣的購物車列表如何實現?我們第一反應就是用ExpandableListView來實現,在上一篇部落格中我們詳細的分析了比較實用而且又炫酷的 RecyclerView的ItemDecoration的知識,用上篇部落格的思路也能實現今天的內容,只不過用RecyclerView的ItemDecoration通過對item設定padding值的方式來預留空間,但是無法設定點選事件。如果你有什麼好的實現方式,歡迎指點。本篇文章則是採用ExpandableListView來實現,實現起來不難,但是關於不同情況的選中狀態等的處理,還是需要仔細考慮周全的。

老規矩,無圖無真相,先來一張效果圖
這裡寫圖片描述

整體功能需求分析

  1. 購物車列表中一般都是一家店鋪(Group)下面會有幾個商品(Child)
  2. 點選店鋪為選中狀態時,那麼該店鋪其下的所有商品應都為選中狀態
  3. 將某一店鋪下面的所有商品都點選為選中狀態時,那麼該店鋪也應該變成選中狀態,同理,當該店鋪下面的商品有一個未選中時,那麼該店鋪的狀態就要變成未選中
  4. 購物車中所有的店鋪都為選中狀態時,或者所有的商品都為選中狀態時,則表示全選

其實,主要相對麻煩點的就是各種狀態的判斷。下面我們來看下adapter的實現,通過繼承BaseExpandableListAdapter,處理父(Group組)與 子(child)的邏輯實現。

public class GoodsExpandableListAdapter extends BaseExpandableListAdapter{

    private Context context;
    private List<GoodsBean> lists;
    private LayoutInflater inflater;
    private CustomDialog dialog;
    private OnSelectedAllListner listener;//回撥介面
    private boolean isSelectedAll = false
; public GoodsExpandableListAdapter(Context context) { this.context = context; inflater = LayoutInflater.from(context); } public void refreshDatas(List<GoodsBean> lists){ this.lists = lists; notifyDataSetChanged(); } @Override public int getGroupCount() { return null != lists ? lists.size() : 0; } @Override public int getChildrenCount(int groupPosition) { return null != lists ? lists.get(groupPosition).getGoods().size() : 0; } @Override public Object getGroup(int groupPosition) { return lists.get(groupPosition); } @Override public Object getChild(int groupPosition, int childPosition) { return lists.get(groupPosition).getGoods().get(childPosition); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public boolean hasStableIds() { return false; } @Override public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { final GroupViewHolder holder; if(convertView == null){ convertView = inflater.inflate(R.layout.item_group_listview, parent, false); holder = new GroupViewHolder(); holder.tvShopName = (TextView) convertView.findViewById(R.id.tv_shopName); holder.ivGroupCheck = (ImageView) convertView.findViewById(R.id.iv_check_group); convertView.setTag(holder); }else{ holder = (GroupViewHolder) convertView.getTag(); } final GoodsBean bean = lists.get(groupPosition); holder.tvShopName.setText(bean.getShopName()); selectedItem(bean.isGroupSelected(), holder.ivGroupCheck); holder.ivGroupCheck.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { boolean isGroupSelected = !bean.isGroupSelected(); bean.setGroupSelected(isGroupSelected);//組的狀態 for(int i = 0; i < lists.get(groupPosition).getGoods().size(); i++){ lists.get(groupPosition).getGoods().get(i).setChildSelected(isGroupSelected);//子的狀態 } //判斷所有組是不是都選中了,都選中的話,通過介面告訴主介面的全選控制元件,並讓其為選中狀態的圖片 boolean isAllGroup = isAllGroupSelected(lists); if(listener != null){ listener.isSelectedAll(isAllGroup); } notifyDataSetChanged(); } }); return convertView; } @Override public View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { final ChildViewHolder holder; if(convertView == null){ convertView = inflater.inflate(R.layout.item_child_listview, parent, false); holder = new ChildViewHolder(); holder.ivGoodsUrl = (ImageView) convertView.findViewById(R.id.iv_goods_url); holder.tvGoodsName = (TextView) convertView.findViewById(R.id.tv_goods_name); holder.tvGoodsOriPrice = (TextView) convertView.findViewById(R.id.tv_goods_ori_price); holder.tvGoodsPrice = (TextView) convertView.findViewById(R.id.tv_goods_price); holder.tvGoodsNum = (TextView) convertView.findViewById(R.id.tv_goods_num); holder.ivChildCheck = (ImageView) convertView.findViewById(R.id.iv_check_child); convertView.setTag(holder); }else{ holder = (ChildViewHolder) convertView.getTag(); } final GoodsBean.GoodsDetailsBean bean = lists.get(groupPosition).getGoods().get(childPosition); holder.tvGoodsName.setText(bean.getGoodsName()); holder.tvGoodsOriPrice.setText(bean.getGoodsOriPrice()); holder.tvGoodsPrice.setText(bean.getGoodsPrice()); holder.tvGoodsNum.setText(bean.getGoodsNum()); //這裡用本地的測試圖片,正式開發則需要從網路取 holder.ivGoodsUrl.setImageResource(bean.getGoodsUrl()); selectedItem(bean.isChildSelected(), holder.ivChildCheck); holder.ivChildCheck.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { boolean isChildSelected = !bean.isChildSelected(); bean.setChildSelected(isChildSelected);//子項的選中與未選中 //還需要進一步處理子項狀態導致的組的狀態問題,如果某一組的子項都選中的話,那麼所在的組也為選中狀態 boolean isSelectedGroup = isAllChildSelected(lists.get(groupPosition).getGoods()); lists.get(groupPosition).setGroupSelected(isSelectedGroup); //因為子項狀態會影響組的狀態,判斷所有組是不是都選中了,都選中的話,通過介面告訴主介面的全選控制元件,並讓其為選中狀態的圖片 boolean isAllGroup = isAllGroupSelected(lists); if(listener != null){ listener.isSelectedAll(isAllGroup); } notifyDataSetChanged(); } }); convertView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //彈窗 dialog dialog = new CustomDialog.Builder(context) .setContent("你確定要刪除該商品嗎?") .setLeftText("容朕三思") .setRightText("朕意已決") .create(); dialog.show(); } }); return convertView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return false; } class GroupViewHolder{ TextView tvShopName;//店鋪名稱 ImageView ivGroupCheck;//選擇按鈕 } class ChildViewHolder{ ImageView ivGoodsUrl;//商品圖片url TextView tvGoodsName;//商品名稱 TextView tvGoodsOriPrice;//原價 TextView tvGoodsPrice;//實際價格 TextView tvGoodsNum;//購買數量 ImageView ivChildCheck;//選擇按鈕 } }

下面再來說說稍微有點麻煩的關於選中狀態與未選中狀態以及全選的邏輯處理,這裡封裝了幾個方法,並且配合著上面給出的adapter中點選事件裡的選中狀態的邏輯,相互配合即可實現類似京東中的效果

/**
     *  選中與未選中狀態下對應的圖片狀態的切換
     * @param isSelected  是否選中
     * @param iv  圖片
     */
    private void selectedItem(boolean isSelected, ImageView iv){
        if(isSelected){
            iv.setImageResource(R.drawable.check_selected);
        }else{
            iv.setImageResource(R.drawable.check_default);
        }
    }

    /**
     *  所有組是不是都為選中狀態,因為某一組選中的話,那麼該組其下的所有子項也都選中了
     * @param mLists
     * @return   true:所有組都選中,相當於全選
     */
    private boolean isAllGroupSelected(List<GoodsBean> mLists){
        for(int i = 0; i < mLists.size(); i++){
            boolean isGroupSelected = mLists.get(i).isGroupSelected();
            if(!isGroupSelected){
                return false;
            }
        }
        return true;
    }

    /**
     *  組內所有的子項是否都選中
     * @param childLists
     * @return  true:表示某一組內所有的子項都選中
     */
    private boolean isAllChildSelected(List<GoodsBean.GoodsDetailsBean> childLists){
        for(int i = 0; i < childLists.size(); i++){
            boolean isChildSelected = childLists.get(i).isChildSelected();
            if(!isChildSelected){
                return false;
            }
        }
        return true;
    }

    /**
     *  全選
     * @param lists
     * @param isSelectedAll
     * @param iv
     * @return
     */
    public boolean isSelectedAll(List<GoodsBean> lists, boolean isSelectedAll, ImageView iv){
        isSelectedAll = !isSelectedAll;
        selectedItem(isSelectedAll, iv);
        for(int i = 0; i < lists.size(); i++){
            lists.get(i).setGroupSelected(isSelectedAll);
            for(int j = 0; j < lists.get(i).getGoods().size(); j++){
                lists.get(i).getGoods().get(j).setChildSelected(isSelectedAll);
            }
        }
        return isSelectedAll;
    }

下面再來分析下全選的情況,我們可以通過介面的方法,將全部選中時的狀態傳遞給MainActivity

public interface OnSelectedAllListner{
        void isSelectedAll(boolean flag);
    }

    public void setOnSelectedAllListner(OnSelectedAllListner listener){
        this.listener = listener;
    }


    public View.OnClickListener getAdapterOnClickListener(){
        return clickListener;
    }

    View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.iv_check_all:  //主介面中全選按鈕控制元件的id
                    isSelectedAll = isSelectedAll(lists, isSelectedAll, (ImageView) v);
                    notifyDataSetChanged();
                    break;
            }
        }
    };

最後看下MainActivity的程式碼,順便模擬一些資料做測試

public class MainActivity extends AppCompatActivity implements GoodsExpandableListAdapter.OnSelectedAllListner{

    private ExpandableListView expandableListView;
    private GoodsExpandableListAdapter adapter;
    private List<GoodsBean> groupLists;//父類
    //private Map<String, List<GoodsBean.GoodsDetailsBean>> map;
    private ImageView ivCheckAll;//全選的圖片按鈕




    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控制元件
        iniViews();
        //測試資料
        setDatas();
        //初始化adapter
        adapter = new GoodsExpandableListAdapter(this);
        expandableListView.setAdapter(adapter);
        adapter.refreshDatas(groupLists);
        //展開所有分組
        for(int i = 0; i < groupLists.size(); i++){
            expandableListView.expandGroup(i);
        }

        //設定ExpandableListView的樣式 去掉預設箭頭 以及禁止原有的展開關閉功能
        expandableListView.setGroupIndicator(null);
        expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
            @Override
            public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
                return true;
            }
        });

        //設定adapter的監聽事件
        adapter.setOnSelectedAllListner(this);

        //通過監聽器關聯Activity和Adapter的關係
        View.OnClickListener listener = adapter.getAdapterOnClickListener();
        if(listener != null){
            ivCheckAll.setOnClickListener(listener);
        }
    }

    private void iniViews() {
        expandableListView = (ExpandableListView) findViewById(R.id.expand_listview);
        ivCheckAll = (ImageView) findViewById(R.id.iv_check_all);//全選
    }

    private void setDatas() {

        groupLists = new ArrayList<>();

        //child 類
        List<GoodsBean.GoodsDetailsBean> childBeanLists1 = new ArrayList<>();
        GoodsBean.GoodsDetailsBean childBean1 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 360", "¥ 260", "2");
        childBeanLists1.add(childBean1);
        //最外層實體類
        GoodsBean bean = new GoodsBean("a1", "京東店鋪", childBeanLists1);
        groupLists.add(bean);

        List<GoodsBean.GoodsDetailsBean> childBeanLists2 = new ArrayList<>();
        GoodsBean.GoodsDetailsBean childBean2 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 200", "¥ 100", "3");
        GoodsBean.GoodsDetailsBean childBean22 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 150", "¥ 90", "6");
        childBeanLists2.add(childBean2);
        childBeanLists2.add(childBean22);
        //最外層實體類
        GoodsBean bean1 = new GoodsBean("a2", "淘寶店鋪", childBeanLists2);
        groupLists.add(bean1);

        List<GoodsBean.GoodsDetailsBean> childBeanLists3 = new ArrayList<>();
        GoodsBean.GoodsDetailsBean childBean3 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 500", "¥ 300", "3");
        GoodsBean.GoodsDetailsBean childBean31 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 650", "¥ 500", "2");
        GoodsBean.GoodsDetailsBean childBean32 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 550", "¥ 40", "9");
        childBeanLists3.add(childBean3);
        childBeanLists3.add(childBean31);
        childBeanLists3.add(childBean32);
        //最外層實體類
        GoodsBean bean2 = new GoodsBean("a3", "天貓店鋪", childBeanLists3);
        groupLists.add(bean2);

        List<GoodsBean.GoodsDetailsBean> childBeanLists4 = new ArrayList<>();
        GoodsBean.GoodsDetailsBean childBean4 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 1200", "¥ 900", "3");
        GoodsBean.GoodsDetailsBean childBean41 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 1600", "¥ 1000", "2");
        GoodsBean.GoodsDetailsBean childBean42 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 1500", "¥ 600", "9");
        GoodsBean.GoodsDetailsBean childBean43 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "時尚揹包 最新潮流 黑色", "¥ 888", "¥ 666", "5");
        childBeanLists4.add(childBean4);
        childBeanLists4.add(childBean41);
        childBeanLists4.add(childBean42);
        childBeanLists4.add(childBean43);
        //最外層實體類
        GoodsBean bean3 = new GoodsBean("a4", "蘇寧易購店鋪", childBeanLists4);
        groupLists.add(bean3);

    }

    @Override
    public void isSelectedAll(boolean flag) {
        if(flag){
            // true  全選
            ivCheckAll.setImageResource(R.drawable.check_selected);
        }else{
            // false  取消全選
            ivCheckAll.setImageResource(R.drawable.check_default);
        }
    }
}

小插曲,正如題目所述,此demo中還用到了大家經常使用的dialog彈窗,為什麼要單獨拿出來說呢,是因為仿照了系統的AlertDialog的原始碼,採用Builder的方式實現,由於在Builder中我們所寫的方法返回的都是this,所以呼叫起來就比較爽了,一路小點的鏈式結構。

/**
 *  自定義dialog  builder模式
 */

public class CustomDialog extends Dialog{


    public CustomDialog(Context context) {
        super(context, R.style.myDialog);
    }

    public static class Builder{
        private Context context;
        private String content;//內容
        private String leftCancle;//左邊取消按鈕顯示的文字
        private String rightSure;//右邊確定按鈕顯示的文字

        //控制元件
        private TextView tvContent;
        private Button btnLeft;
        private Button btnRight;
        //設定監聽事件
        private View.OnClickListener leftListener;
        private View.OnClickListener rightListener;


        public Builder(Context context) {
            this.context = context;
        }

        public Builder setContent(String content){
            this.content = content;
            return this;
        }

        public Builder setLeftText(String leftText){
            this.leftCancle = leftText;
            return this;
        }

        public Builder setRightText(String rightText){
            this.rightSure = rightText;
            return this;
        }

        public Builder setLeftOnclick(View.OnClickListener leftListener){
            this.leftListener = leftListener;
            return this;
        }

        public Builder setRightOnClick(View.OnClickListener rightListener){
            this.rightListener = rightListener;
            return this;
        }

        public CustomDialog create(){
            CustomDialog dialog = new CustomDialog(context);
            View view = LayoutInflater.from(context).inflate(R.layout.dialog_layout, null);
            tvContent = (TextView) view.findViewById(R.id.tvContent);
            btnLeft = (Button) view.findViewById(R.id.btnLeft);
            btnRight = (Button) view.findViewById(R.id.btnRight);

            if(!TextUtils.isEmpty(content)){
                tvContent.setText(content);
            }
            if(!TextUtils.isEmpty(leftCancle)){
                btnLeft.setText(leftCancle);
            }
            if(!TextUtils.isEmpty(rightSure)){
                btnRight.setText(rightSure);
            }

            if(leftListener != null){
                btnLeft.setOnClickListener(leftListener);
            }
            if(rightListener != null){
                btnRight.setOnClickListener(rightListener);
            }

            //根據螢幕寬來設定dialog的寬高
            Window window = dialog.getWindow();
            window.getDecorView().setPadding(0, 0, 0, 0);
            WindowManager.LayoutParams params = window.getAttributes();
            //獲取手機螢幕寬度
            DisplayMetrics metrics = context.getResources().getDisplayMetrics();

            params.width = (int) (metrics.widthPixels * 0.8);
            window.setAttributes(params);

            window.setGravity(Gravity.CENTER);
            //將佈局載入到dialog上
            dialog.setContentView(view);
            dialog.setCanceledOnTouchOutside(true);

            return dialog;
        }

    }
}

呼叫起來也很優雅

//彈窗 dialog
dialog = new CustomDialog.Builder(context)
        .setContent("你確定要刪除該商品嗎?")
        .setLeftText("容朕三思")
        .setRightText("朕意已決")
        .setLeftOnclick(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dialog.dismiss();
            }
        })
        .setRightOnClick(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dialog.dismiss();
            }
        })
        .create();
dialog.show();

好了,主要的一些知識點都簡單的做了分析,因為這些知識點在實際開發中還是比較常用的。如果你也在寫加入購物車這塊內容的話,那麼,直接複製貼上吧,幫你節省很多時間。主要程式碼已經都貼出來了,原始碼就不上傳了,如果想要原始碼的話,請說一下