高仿淘寶購物車分分鐘讓你整合
前言
做商城類電商app購物車確實一直是一個難點,為什麼難呢?
主要原因呢是他裡面的邏輯複雜,然後 百度的資源好像都不太理想,好多就是一個簡單的listView來實現根本就達不到開發的需求。然後 一般都涉及到了店鋪概念,就不再是一個簡單listView能解決 的,如果用2個listView來巢狀的話涉及到批量操作和商品的勾選以及單個商品的或整個店鋪商品的操作,那樣邏輯變複雜了,然後動不動要用map去儲存勾選狀態,時不時出現position的錯位和陣列下標越界等,而且效能感覺不太好。
這裡我使用的ExpandableListView,然後要完全實現淘寶購物車也是有難度的,由於能力也是有限這裡也是參考了一些人的然後搞了好幾天才大致實現了淘寶購物車功能。
本篇的效果:(如下4張圖)
——————————— pic1————————
——————————— pic2————————
——————————— pic3————————
——————————— pic4————————
實現思路
主佈局就是一個ExpandableListView,然後top的title顯示購物車的商品數量,當刪除某個商品需動態更新,右上角編輯按鈕改變地步遮罩層的佈局並且執行相關的操作,bottom是一個遮罩層編輯時顯示刪除不編輯時可以去結算。然後child的佈局也是通過group的編輯狀態來顯示不同的佈局,編輯狀態下需要改變商品的數量和移除商品。
例項程式碼演示
先來購物車主介面:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/top_bar"
android:layout_width ="match_parent"
android:layout_height="48dp"
android:background="@drawable/topbar_background"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@android:color/transparent"
android:orientation="vertical" >
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_gravity="center_vertical"
android:padding="12dp"
android:src="@mipmap/topbar_up" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="48dp"
android:text="購物車"
android:textColor="#1a1a1a"
android:textSize="16sp" />
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="40dp"
android:gravity="center"
android:minHeight="48dp"
android:text="編輯"
android:textColor="#1a1a1a"
android:textSize="14sp"
android:visibility="visible" />
</RelativeLayout>
</LinearLayout>
<ExpandableListView
android:id="@+id/exListView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:childIndicator="@null"
android:groupIndicator="@null" >
</ExpandableListView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:orientation="horizontal" >
<CheckBox
android:id="@+id/all_chekbox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:layout_marginRight="4dp"
android:button="@drawable/check_box_bg"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:gravity="center"
android:minHeight="64dp"
android:layout_marginLeft="10dp"
android:text="全選"
android:textAppearance="?android:attr/textAppearanceLarge"
/>
<LinearLayout
android:id="@+id/ll_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginRight="20dp"
android:layout_weight="1"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="right"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="合計:"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_total_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="¥0.00"
android:textColor="@color/orangered"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="不含運費"
android:gravity="right"
android:textColor="@color/gray"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/tv_go_to_pay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="3"
android:background="@color/orange"
android:clickable="true"
android:gravity="center"
android:text="結算(0)"
android:textColor="#FAFAFA"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_shar"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:orientation="horizontal"
android:visibility="gone"
>
<TextView
android:id="@+id/tv_share"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_weight="1"
android:layout_marginLeft="5dp"
android:text="分享寶貝"
android:textColor="@color/white"
android:background="@color/orange"
android:textSize="16sp"
android:layout_marginRight="5dp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_save"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="移到收藏夾"
android:background="@color/orange"
android:textColor="@color/white"
android:layout_marginRight="5dp"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_delete"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/crimson"
android:clickable="true"
android:gravity="center"
android:text="刪除"
android:textColor="#FAFAFA"
/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
大致的效果圖:
接下來我們來看child的 佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#CCCCCC" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/page_backgroup"
android:orientation="horizontal" >
<CheckBox
android:id="@+id/check_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:layout_marginRight="4dp"
android:button="@drawable/check_box_bg"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:gravity="center"
android:minHeight="64dp"
android:minWidth="32dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="visible" />
<ImageView
android:id="@+id/iv_adapter_list_pic"
android:layout_width="85dp"
android:layout_height="85dp"
android:layout_marginBottom="15dp"
android:layout_marginTop="13dp"
android:scaleType="centerCrop"
android:src="@drawable/goods1" />
<RelativeLayout
android:id="@+id/rl_no_edtor"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="13dp"
>
<TextView
android:id="@+id/tv_intro"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_marginTop="20dp"
android:ellipsize="end"
android:maxLines="2"
android:text="第八號當鋪美女一枚"
android:textColor="@color/grey_color1"
android:textSize="@dimen/txt_14" />
<TextView
android:id="@+id/tv_color_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="顏色:黑色;尺碼:29"
android:textColor="@color/gray"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginBottom="20dp"
android:layout_alignParentStart="true">
<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:singleLine="true"
android:text="¥ 308.00"
android:textColor="@color/orange_color"
android:textSize="@dimen/txt_14"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_discount_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/tv_price"
android:text=""
android:textColor="@color/gray"
android:textSize="@dimen/txt_10"
/>
<TextView
android:id="@+id/tv_buy_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="20dp"
android:layout_alignParentRight="true"
android:text="X 1"
android:textColor="@color/gray"
android:textSize="@dimen/txt_10"
/>
</RelativeLayout>
</RelativeLayout>
<LinearLayout
android:id="@+id/ll_edtor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="13dp"
android:visibility="gone"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_change_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:layout_marginTop="10dp"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_reduce"
android:layout_width="35dp"
android:layout_height="35dp"
android:background="@drawable/text_angle_gray"
android:gravity="center"
android:text="一"
android:textColor="@color/grey_color1"
android:textSize="@dimen/txt_12" />
<TextView
android:id="@+id/tv_num"
android:layout_width="35dp"
android:layout_height="35dp"
android:background="@drawable/text_angle"
android:gravity="center"
android:singleLine="true"
android:text="1"
android:textColor="@color/grey_color1"
android:textSize="@dimen/txt_12" />
<TextView
android:id="@+id/tv_add"
android:layout_width="35dp"
android:layout_height="35dp"
android:background="@drawable/text_angle_right"
android:gravity="center"
android:text="+"
android:textColor="@color/grey_color1"
android:textSize="@dimen/txt_12" />
</LinearLayout>
<TextView
android:id="@+id/tv_colorsize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="顏色:黑色;尺碼:29"
android:layout_gravity="left"
android:textColor="@color/gray"/>
</LinearLayout>
<TextView
android:id="@+id/tv_goods_delete"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="3"
android:text="刪除"
android:background="@color/orange"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/white"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
大致效果就是這樣的:
然後我們就來重點的講程式碼了:
編輯按鈕的點選處理:
private int flag = 0;//設定按鈕點選的標誌位
//group的按鈕通過flag動態為(編輯/完成)
gholder.store_edtor.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//回掉介面通過groupPosition處理group的編輯狀態
mListener.groupEdit(groupPosition);
if (flag == 0) {
group.setIsEdtor(true);
gholder.store_edtor.setText("完成");
} else if (flag == 1) {
group.setIsEdtor(false);
gholder.store_edtor.setText("編輯");
}
flag = (flag + 1) % 2;//其餘得到迴圈執行上面2個不同的功能
}
});
介面回撥的設定
checkBox的多選全選反選介面的回撥:
/**
* 複選框介面
*/
public interface CheckInterface {
/**
* 組選框狀態改變觸發的事件
*
* @param groupPosition 組元素位置
* @param isChecked 組元素選中與否
*/
public void checkGroup(int groupPosition, boolean isChecked);
/**
* 子選框狀態改變時觸發的事件
*
* @param groupPosition 組元素位置
* @param childPosition 子元素位置
* @param isChecked 子元素選中與否
*/
public void checkChild(int groupPosition, int childPosition, boolean isChecked);
}
通過監聽checkBox的狀態設定group和全選的checkBox的勾選狀態,便於刪除和去結算。
child商品的數量增減和刪除介面回撥
/**
* 改變數量的介面
*/
public interface ModifyCountInterface {
/**
* 增加操作
*
* @param groupPosition 組元素位置
* @param childPosition 子元素位置
* @param showCountView 用於展示變化後數量的View
* @param isChecked 子元素選中與否
*/
public void doIncrease(int groupPosition, int childPosition, View showCountView, boolean isChecked);
/**
* 刪減操作
*
* @param groupPosition 組元素位置
* @param childPosition 子元素位置
* @param showCountView 用於展示變化後數量的View
* @param isChecked 子元素選中與否
*/
public void doDecrease(int groupPosition, int childPosition, View showCountView, boolean isChecked);
/**
* 刪除子item
* @param groupPosition
* @param childPosition
*/
public void childDelete(int groupPosition,int childPosition);
}
通過監聽child的商品數量的變化,從而計算 購物車結算時的金額和數量,當店鋪的商品刪除完的時候順便把店鋪也移除出購物車。
group的編輯狀態的回撥
/**
* 監聽group編輯狀態
*/
public interface GroupEdtorListener{
public void groupEdit(int groupPosition);
}
通過監聽group的狀態,動態 設定child的佈局並且進行相關的新增和減少商品,並且還能移除商品出購物車。
相關購物車的操作
購物車的刪除
/**
* 刪除操作<br>
* 1.不要邊遍歷邊刪除,容易出現數組越界的情況<br>
* 2.現將要刪除的物件放進相應的列表容器中,待遍歷完後,以removeAll的方式進行刪除
*/
protected void doDelete() {
List<StoreInfo> toBeDeleteGroups = new ArrayList<StoreInfo>();// 待刪除的組元素列表
for (int i = 0; i < groups.size(); i++) {
StoreInfo group = groups.get(i);
if (group.isChoosed()) {
toBeDeleteGroups.add(group);
}
List<GoodsInfo> toBeDeleteProducts = new ArrayList<GoodsInfo>();// 待刪除的子元素列表
List<GoodsInfo> childs = children.get(group.getId());
for (int j = 0; j < childs.size(); j++) {
if (childs.get(j).isChoosed()) {
toBeDeleteProducts.add(childs.get(j));
}
}
childs.removeAll(toBeDeleteProducts);
}
groups.removeAll(toBeDeleteGroups);
selva.notifyDataSetChanged();
calculate();
}
購物車數量增加
@Override
public void doIncrease(int groupPosition, int childPosition,
View showCountView, boolean isChecked) {
GoodsInfo product = (GoodsInfo) selva.getChild(groupPosition,
childPosition);
int currentCount = product.getCount();
currentCount++;
product.setCount(currentCount);
((TextView) showCountView).setText(currentCount + "");
selva.notifyDataSetChanged();
calculate();
}
購物車商品數量減少
@Override
public void doDecrease(int groupPosition, int childPosition,View showCountView, boolean isChecked) {
GoodsInfo product = (GoodsInfo) selva.getChild(groupPosition,
childPosition);
int currentCount = product.getCount();
if (currentCount == 1)
return;
currentCount--;
product.setCount(currentCount);
((TextView) showCountView).setText(currentCount + "");
selva.notifyDataSetChanged();
calculate();
}
購物車物品的勾選狀態變化
@Override
public void checkGroup(int groupPosition, boolean isChecked) {//判斷group是否勾選
StoreInfo group = groups.get(groupPosition);
List<GoodsInfo> childs = children.get(group.getId());
for (int i = 0; i < childs.size(); i++) {
childs.get(i).setChoosed(isChecked);
}
if (isAllCheck())
allChekbox.setChecked(true);
else
allChekbox.setChecked(false);
selva.notifyDataSetChanged();
calculate();
}
@Override
public void checkChild(int groupPosition, int childPosiTion, boolean isChecked) {//判斷child是否勾選
boolean allChildSameState = true;// 判斷改組下面的所有子元素是否是同一種狀態
StoreInfo group = groups.get(groupPosition);
List<GoodsInfo> childs = children.get(group.getId());
for (int i = 0; i < childs.size(); i++) {
// 不全選中
if (childs.get(i).isChoosed() != isChecked) {
allChildSameState = false;
break;
}
}
//獲取店鋪選中商品的總金額
if (allChildSameState) {
group.setChoosed(isChecked);// 如果所有子元素狀態相同,那麼對應的組元素被設為這種統一狀態
} else {
group.setChoosed(false);// 否則,組元素一律設定為未選中狀態
}
if (isAllCheck()) {
allChekbox.setChecked(true);// 全選
} else {
allChekbox.setChecked(false);// 反選
}
selva.notifyDataSetChanged();
calculate();
}
private boolean isAllCheck() {//是否全選
for (StoreInfo group : groups) {
if (!group.isChoosed())
return false;
}
return true;
}
/**
* 全選與反選
*/
private void doCheckAll() {
groups.get(i).setChoosed(allChekbox.isChecked());
StoreInfo group = groups.get(i);
List<GoodsInfo> childs = children.get(group.getId());
for (int j = 0; j < childs.size(); j++) {
childs.get(j).setChoosed(allChekbox.isChecked());
}
}
selva.notifyDataSetChanged();
calculate();
}
購物車結算金額的計算
/**
* 統計操作<br>
* 1.先清空全域性計數器<br>
* 2.遍歷所有子元素,只要是被選中狀態的,就進行相關的計算操作<br>
* 3.給底部的textView進行資料填充
*/
private void calculate() {
totalCount = 0;
totalPrice = 0.00;
for (int i = 0; i < groups.size(); i++) {
StoreInfo group = groups.get(i);
List<GoodsInfo> childs = children.get(group.getId());
for (int j = 0; j < childs.size(); j++) {
GoodsInfo product = childs.get(j);
if (product.isChoosed()) {
totalCount++;
totalPrice += product.getPrice() * product.getCount();
}
}
}
tvTotalPrice.setText("¥" + totalPrice);
tvGoToPay.setText("去支付(" + totalCount + ")");
}
好了,前面主要的 邏輯程式碼都貼的差不多了,確實也看的比較瑣碎,不坑大家了直接上2個 完整類的程式碼:
package com.zy.tbshoppingcart.adapter;
import android.content.Context;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.StrikethroughSpan;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.zy.tbshoppingcart.R;
import com.zy.tbshoppingcart.entity.GoodsInfo;
import com.zy.tbshoppingcart.entity.StoreInfo;
import java.util.List;
import java.util.Map;
/**
* 購物車資料介面卡
*/
public class ShopcartAdapter extends BaseExpandableListAdapter {
private List<StoreInfo> groups;
private Map<String, List<GoodsInfo>> children;
private Context context;
private CheckInterface checkInterface;
private ModifyCountInterface modifyCountInterface;
private int flag = 0;
private GroupEdtorListener mListener;
public GroupEdtorListener getmListener() {
return mListener;
}
public void setmListener(GroupEdtorListener mListener) {
this.mListener = mListener;
}
/**
* 建構函式
*
* @param groups 組元素列表
* @param children 子元素列表
* @param context
*/
public ShopcartAdapter(List<StoreInfo> groups, Map<String, List<GoodsInfo>> children, Context context) {
this.groups = groups;
this.children = children;
this.context = context;
}
public void setCheckInterface(CheckInterface checkInterface) {
this.checkInterface = checkInterface;
}
public void setModifyCountInterface(ModifyCountInterface modifyCountInterface) {
this.modifyCountInterface = modifyCountInterface;
}
@Override
public int getGroupCount() {
return groups.size();
}
@Override
public int getChildrenCount(int groupPosition) {
String groupId = groups.get(groupPosition).getId();
return children.get(groupId).size();
}
@Override
public Object getGroup(int groupPosition) {
return groups.get(groupPosition);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
List<GoodsInfo> childs = children.get(groups.get(groupPosition).getId());