仿淘寶購物車

雙11剛過伶氢,感覺淘寶購物車裆悄,你挺強大呀。雖然在淘寶上買不起倚聚,但是我可以自己做一個購物車自己買過把癮夕吻。就想著自己也來仿著做一個吧诲锹。這個叫李博程序員十分不容易,白天上班梭冠,常常晚上寫項目辕狰,寫博客到半夜3點,希望大家多多支持一下吧控漠。
github代碼直通車

啥也不說了蔓倍,先上效果圖:


giphy.gif
購物車重要術(shù)語: 單品悬钳,商品,SKU,SPU

商品:淘寶叫item偶翅,京東叫product默勾,商品特指與商家有關(guān)的商品,每個商品有一個商家編碼聚谁,每個商品下面有多個顏色母剥,款式,大小形导,每種組合的笛卡爾積為一個SKU环疼。

SKU:Stock Keeping Unit(庫存量單位),SKU即庫存進出計量的單位朵耕, 可以是以件炫隶、盒、托盤等為單位阎曹。在服裝伪阶、鞋類商品中使用最多最普遍。例如紡織品中一個SKU通常表示:規(guī)格处嫌、顏色栅贴、款式。一個商品可以有多個sku熏迹。

SPU:Standard Product Unit (標(biāo)準(zhǔn)化產(chǎn)品單元)檐薯,SPU是商品信息聚合的最小單位,是一組可復(fù)用癣缅、易檢索的標(biāo)準(zhǔn)化信息的集合厨剪,該集合描述了一個產(chǎn)品的特性。通俗點講友存,屬性值、特性相同的商品就可以稱為一個SPU陶衅。例如iphone8的64G屡立,黑色等售賣的屬性就是spu屬性。一個商品有一個spu搀军。

購物車應(yīng)該有的其他功能:
  • 支付之前可選:優(yōu)惠券膨俐、打折券、滿減券等(用戶通過活動罩句,購買返現(xiàn)焚刺,關(guān)注公眾號,搶紅包等獲得)门烂。
  • 訂單狀態(tài)跟蹤:狀態(tài)可以包括未付款乳愉,已付款兄淫,備貨,配送中蔓姚,確認(rèn)收貨捕虽,取消訂單,退款中坡脐,退款成功泄私,線下自取。
  • 防止刷單機制:獲取設(shè)備IMEI备闲,0就是模擬器晌端,后臺應(yīng)判斷該設(shè)備不能創(chuàng)建訂單。
  • 訂單失效:30分鐘支付時間恬砂,未支付應(yīng)該恢復(fù)SKU斩松。

該購物車功能包括了選擇商品,增減商品數(shù)量觉既,計算總價惧盹,全選,全不選功能瞪讼。需要接入結(jié)算功能請看我的博客微信支付寶接入钧椰。

item實體類:
public class ShopcartEntity {
    /**
     * product_id : 53   商品id
     * quantity : 4      購物車選擇數(shù)量
     * product_name : 商品名稱
     * product_price :  商品價格
     * product_quantity : 20   庫存
     * picRes :  圖片資源res,這里用的本地圖片
     * product_status :  訂單狀態(tài)
     */
    private int id;
    private int product_id;
    private int quantity;
    private String product_name;
    private String product_price;
    private int product_quantity;
    private int picRes;
    private String product_status;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setProduct_id(int product_id) {
        this.product_id = product_id;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public void setProduct_name(String product_name) {
        this.product_name = product_name;
    }

    public void setProduct_price(String product_price) {
        this.product_price = product_price;
    }

    public void setProduct_quantity(int product_quantity) {
        this.product_quantity = product_quantity;
    }

    public int getPicRes() {
        return picRes;
    }

    public void setPicRes(int picRes) {
        this.picRes = picRes;
    }

    public void setProduct_status(String product_status) {
        this.product_status = product_status;
    }

    public int getProduct_id() {
        return product_id;
    }

    public int getQuantity() {
        return quantity;
    }

    public String getProduct_name() {
        return product_name;
    }

    public String getProduct_price() {
        return product_price;
    }

    public int getProduct_quantity() {
        return product_quantity;
    }

    public String getProduct_status() {
        return product_status;
    }

}

功能實現(xiàn)流程:

1.adapter.registerAdapterDataObserver(totalPriceObserver)符欠,給adapter添加數(shù)據(jù)變化監(jiān)聽類嫡霞,一旦有增減商品,在onChanged()回調(diào)中重新計算總價

2.用SparseArray優(yōu)化集合存儲checkbox選擇了的商品希柿,類似于hashmap诊沪,商品id作為鍵,列表當(dāng)前position的checkbox選中狀態(tài)boolean作為值曾撤,這個數(shù)據(jù)需要計算總價端姚。

3.calculateTotalPrice()方法:遍歷選中商品,用id匹配得到商品entity挤悉,該項價格=選中數(shù)量*該商品單價渐裸,再累加到總價

4.全選,將所有列表數(shù)據(jù)添加打sparseArray中装悲。全部選昏鹃,clear()清除全部數(shù)據(jù)。

功能實現(xiàn)類:
public class ShopCartActivity extends AppCompatActivity implements View.OnClickListener {

    @Bind(R.id.tv_nodatas)
    TextView tvNodatas;
    @Bind(R.id.tv_shopcart_totalmoney)
    TextView tvShopcartTotalmoney;
    @Bind(R.id.cb_shopcart_all)
    CheckBox cbShopcartAll;
    @Bind(R.id.tv_billing)
    TextView tvBilling;
    @Bind(R.id.rv)
    RecyclerView rv;
    private int[] pics = {R.mipmap.test1, R.mipmap.test2, R.mipmap.test3, R.mipmap.test4};
    private ArrayList<ShopcartEntity> datas = new ArrayList();
    private CommonAdapter<ShopcartEntity> adapter;
    /**
     * 用來記錄checkBox列表當(dāng)前選中狀態(tài)诀诊,購物車id是鍵洞渤,是否選中狀態(tài)是值
     */
    private SparseArray<Boolean> mSelectState = new SparseArray();
    /**
     * 購物車商品總價格
     */
    private float totalMoney = 0;
    /**
     * 創(chuàng)建數(shù)量改變觀察者對象
     */
    private RecyclerView.AdapterDataObserver totalPriceObserver = new RecyclerView.AdapterDataObserver() {

        /**
         * 當(dāng)Adapter的notifyDataSetChanged方法執(zhí)行時被調(diào)用
         */
        public void onChanged() {
            calculateTotalPrice();
        }

        /**
         * 當(dāng)Adapter 調(diào)用 notifyDataSetInvalidate方法執(zhí)行時被調(diào)用
         */
        public void onInvalidated() {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_shop_cart);
        ButterKnife.bind(this);

        initView();
    }

    private void initView() {

        rv.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
        adapter = new CommonAdapter<ShopcartEntity>(getApplicationContext(), R.layout.item_shopcart, datas) {
            @Override
            protected void convert(ViewHolder baseViewHolder, final ShopcartEntity entity, final int position) {
                final CheckBox cbChoose = baseViewHolder.getView(R.id.cb_shopcart);
                ImageView ivCover = baseViewHolder.getView(R.id.iv_shopcart_cover);
                TextView tvName = baseViewHolder.getView(R.id.tv_shopcart_name);
                TextView tvPrice = baseViewHolder.getView(R.id.tv_shopcart_price);
                ImageButton ibDel = baseViewHolder.getView(R.id.ib_shopcart_del);
                final TextView tvReduce = baseViewHolder.getView(R.id.tv_detail_reduce);
                TextView tvPlus = baseViewHolder.getView(R.id.tv_detail_plus);
                final TextView tvNum = baseViewHolder.getView(R.id.tv_detail_productnum);

                ivCover.setImageResource(entity.getPicRes());
                tvName.setText(entity.getProduct_name());
                tvPrice.setText(entity.getProduct_price());
                tvNum.setText("" + entity.getQuantity());

                final int id = entity.getId();
                cbChoose.setChecked(mSelectState.get(id, false));
                cbChoose.setOnClickListener(new View.OnClickListener() {     //用onclick方法而不是onChecked方法,因為是自動調(diào)用onCheckedChange方法
                    @Override
                    public void onClick(View v) {
                        //通過保存的是否選中來判斷操作
                        boolean isSelected = !mSelectState.get(id,false);
                        cbChoose.setChecked(isSelected);
                        if(isSelected){
                            mSelectState.put(id, true);
                        }else{
                            mSelectState.delete(id);
                        }
                        cbShopcartAll.setChecked(mSelectState.size() == datas.size());   //判斷是否達(dá)到全選
                        notifyDataSetChanged();
                    }
                });

                tvReduce.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int quatity = (datas.get(position)).getQuantity();
                        if(quatity == 1) return;
                        (datas.get(position)).setQuantity(quatity - 1);
                        notifyDataSetChanged();
                    }
                });
                tvPlus.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int quatity = (datas.get(position)).getQuantity();
                        if(quatity >= entity.getProduct_quantity()){
                            Toast.makeText(getApplicationContext(),"超出庫存量", Toast.LENGTH_SHORT).show();
                            return;
                        }
                        (datas.get(position)).setQuantity(quatity + 1);
                        notifyDataSetChanged();
                    }
                });
            }
        };
        rv.setAdapter(adapter);
        adapter.registerAdapterDataObserver(totalPriceObserver);

        cbShopcartAll.setOnClickListener(this);
        tvBilling.setOnClickListener(this);

        initData();
    }

    /**
     * 模擬服務(wù)器數(shù)據(jù)
     */
    private void initData() {
        ArrayList list = new ArrayList();
        ShopcartEntity entity;
        for (int i = 0; i < pics.length; i++) {
            entity = new ShopcartEntity();
            entity.setId(i);
            entity.setProduct_id(i);
            entity.setProduct_name("商品" + i);
            entity.setProduct_price("199");
            entity.setProduct_status("selling");
            entity.setPicRes(pics[i]);
            entity.setQuantity(1);
            entity.setProduct_quantity(5);
            list.add(entity);
        }
        datas.addAll(list);
        adapter.notifyDataSetChanged();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.cb_shopcart_all:
                checkAll();
                break;
            case R.id.tv_billing:
                //判斷是否有一項商品的選擇
                if (mSelectState.size() == 0) {
                    Toast.makeText(getApplicationContext(), "未選擇商品", Toast.LENGTH_SHORT).show();
                } else {
                    //去結(jié)算
                }
                break;
        }
    }

    private void calculateTotalPrice() {
        totalMoney = 0;
        for (int i = 0; i < mSelectState.size(); i++) {
            for (ShopcartEntity entity : datas) {
                if (mSelectState.keyAt(i) == entity.getId()) {    //表明選中了當(dāng)前這項
                    totalMoney += entity.getQuantity() * Float.parseFloat(entity.getProduct_price());
                }
            }
        }
        tvShopcartTotalmoney.setText("" + totalMoney);
    }

    private void checkAll() {
        mSelectState.clear();
        if (cbShopcartAll.isChecked()) {   //全選
            for (int i = 0; i < datas.size(); i++) {
                int id = datas.get(i).getId();
                mSelectState.put(id, true);
            }
            adapter.notifyDataSetChanged();
        } else {   //全不選
            adapter.notifyDataSetChanged();
        }
    }

}

購物車是個危險的東西属瓣,稍不注意就被剁手载迄。喜歡我讯柔,就點我吧!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宪巨,一起剝皮案震驚了整個濱河市磷杏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捏卓,老刑警劉巖极祸,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異怠晴,居然都是意外死亡遥金,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門蒜田,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稿械,“玉大人,你說我怎么就攤上這事冲粤∶滥” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵梯捕,是天一觀的道長厢呵。 經(jīng)常有香客問我,道長傀顾,這世上最難降的妖魔是什么襟铭? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮短曾,結(jié)果婚禮上寒砖,老公的妹妹穿的比我還像新娘。我一直安慰自己嫉拐,他們只是感情好哩都,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著椭岩,像睡著了一般茅逮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上判哥,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音碉考,去河邊找鬼塌计。 笑死,一個胖子當(dāng)著我的面吹牛侯谁,可吹牛的內(nèi)容都是我干的锌仅。 我是一名探鬼主播章钾,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼热芹!你這毒婦竟也來了贱傀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤伊脓,失蹤者是張志新(化名)和其女友劉穎府寒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體报腔,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡株搔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了纯蛾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纤房。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翻诉,靈堂內(nèi)的尸體忽然破棺而出炮姨,到底是詐尸還是另有隱情,我是刑警寧澤碰煌,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布舒岸,位于F島的核電站,受9級特大地震影響拄查,放射性物質(zhì)發(fā)生泄漏吁津。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一堕扶、第九天 我趴在偏房一處隱蔽的房頂上張望碍脏。 院中可真熱鬧,春花似錦稍算、人聲如沸典尾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钾埂。三九已至,卻和暖如春科平,著一層夾襖步出監(jiān)牢的瞬間褥紫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工瞪慧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留髓考,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓弃酌,卻偏偏與公主長得像氨菇,于是被迫代替她去往敵國和親儡炼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容