雙11剛過,感覺淘寶購物車,你挺強大呀。雖然在淘寶上買不起,但是我可以自己做一個購物車自己買過把癮。就想著自己也來仿著做一個吧。這個叫李博程序員十分不容易,白天上班,常常晚上寫項目,寫博客到半夜3點,希望大家多多支持一下吧。
github代碼直通車
啥也不說了,先上效果圖:
購物車重要術語: 單品,商品,SKU,SPU
商品:淘寶叫item,京東叫product,商品特指與商家有關的商品,每個商品有一個商家編碼,每個商品下面有多個顏色,款式,大小,每種組合的笛卡爾積為一個SKU。
SKU:Stock Keeping Unit(庫存量單位),SKU即庫存進出計量的單位, 可以是以件、盒、托盤等為單位。在服裝、鞋類商品中使用最多最普遍。例如紡織品中一個SKU通常表示:規格、顏色、款式。一個商品可以有多個sku。
SPU:Standard Product Unit (標準化產品單元),SPU是商品信息聚合的最小單位,是一組可復用、易檢索的標準化信息的集合,該集合描述了一個產品的特性。通俗點講,屬性值、特性相同的商品就可以稱為一個SPU。例如iphone8的64G,黑色等售賣的屬性就是spu屬性。一個商品有一個spu。
購物車應該有的其他功能:
- 支付之前可選:優惠券、打折券、滿減券等(用戶通過活動,購買返現,關注公眾號,搶紅包等獲得)。
- 訂單狀態跟蹤:狀態可以包括未付款,已付款,備貨,配送中,確認收貨,取消訂單,退款中,退款成功,線下自取。
- 防止刷單機制:獲取設備IMEI,0就是模擬器,后臺應判斷該設備不能創建訂單。
- 訂單失效:30分鐘支付時間,未支付應該恢復SKU。
該購物車功能包括了選擇商品,增減商品數量,計算總價,全選,全不選功能。需要接入結算功能請看我的博客微信支付寶接入。
item實體類:
public class ShopcartEntity {
/**
* product_id : 53 商品id
* quantity : 4 購物車選擇數量
* product_name : 商品名稱
* product_price : 商品價格
* product_quantity : 20 庫存
* picRes : 圖片資源res,這里用的本地圖片
* product_status : 訂單狀態
*/
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;
}
}
功能實現流程:
1.adapter.registerAdapterDataObserver(totalPriceObserver),給adapter添加數據變化監聽類,一旦有增減商品,在onChanged()回調中重新計算總價
2.用SparseArray優化集合存儲checkbox選擇了的商品,類似于hashmap,商品id作為鍵,列表當前position的checkbox選中狀態boolean作為值,這個數據需要計算總價。
3.calculateTotalPrice()方法:遍歷選中商品,用id匹配得到商品entity,該項價格=選中數量*該商品單價,再累加到總價
4.全選,將所有列表數據添加打sparseArray中。全部選,clear()清除全部數據。
功能實現類:
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列表當前選中狀態,購物車id是鍵,是否選中狀態是值
*/
private SparseArray<Boolean> mSelectState = new SparseArray();
/**
* 購物車商品總價格
*/
private float totalMoney = 0;
/**
* 創建數量改變觀察者對象
*/
private RecyclerView.AdapterDataObserver totalPriceObserver = new RecyclerView.AdapterDataObserver() {
/**
* 當Adapter的notifyDataSetChanged方法執行時被調用
*/
public void onChanged() {
calculateTotalPrice();
}
/**
* 當Adapter 調用 notifyDataSetInvalidate方法執行時被調用
*/
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方法,因為是自動調用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()); //判斷是否達到全選
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();
}
/**
* 模擬服務器數據
*/
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 {
//去結算
}
break;
}
}
private void calculateTotalPrice() {
totalMoney = 0;
for (int i = 0; i < mSelectState.size(); i++) {
for (ShopcartEntity entity : datas) {
if (mSelectState.keyAt(i) == entity.getId()) { //表明選中了當前這項
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();
}
}
}
購物車是個危險的東西,稍不注意就被剁手。喜歡我,就點我吧!