雙11剛過伶氢,感覺淘寶購物車裆悄,你挺強大呀。雖然在淘寶上買不起倚聚,但是我可以自己做一個購物車自己買過把癮夕吻。就想著自己也來仿著做一個吧诲锹。這個叫李博程序員十分不容易,白天上班梭冠,常常晚上寫項目辕狰,寫博客到半夜3點,希望大家多多支持一下吧控漠。
github代碼直通車
啥也不說了蔓倍,先上效果圖:
購物車重要術(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();
}
}
}
購物車是個危險的東西属瓣,稍不注意就被剁手载迄。喜歡我讯柔,就點我吧!