Android 美團購物車效果


轉載:https://www.jianshu.com/p/e4e0c8302563

 

老規矩先上效果圖
![美團購物車.gif](https://upload-images.jianshu.io/upload_images/8375678-ca40caab322f52ff.gif?imageMogr2/auto-orient/strip)
GIF圖有點不清楚,再上兩張截圖
![Wec1111.jpeg](https://upload-images.jianshu.io/upload_images/8375678-01c72a74dfad9f36.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![Wech222.jpeg](https://upload-images.jianshu.io/upload_images/8375678-a95952d73ad08b2d.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


項目地址:https://gitee.com/dingxiansen/Vehicle-keyboard-android/tree/master/meituanshoppingcart 需要的自取

效果就是gif展示的,效果圖有了,還是要用文字介紹下的。
效果就是左右兩個列表,左側列表點擊時,右側的標題自動顯示到列表的頂部,標題是懸浮吸頂的,沒組的標題固定懸浮在頂部,當右側列表滑動時,左側列表自動定位至和左側相同的分類保持統一,底部的彈出購物車區域,購物車的高度是在屏幕的70%以下是自適應的高度,最大高度是當前屏幕的70%,下邊是部分代碼和實現思路。

這個效果主要要處理的就是兩個RecyclerView 的互相交互和數據處理
![image.png](https://upload-images.jianshu.io/upload_images/8375678-3f4c2d741b697b25.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這就是整個頁面的主要布局,兩個recyclerView

至於右側又一個熱銷水果的標題,其實使用StickyHeaderLayoutManager 也可以使用標題吸頂,但是使用這個類的話,在后邊左側點擊讓右側顯示到頂部的時候會特別難處理,而且StickyHeaderLayoutManager這個Manager里邊也沒有recyclerView的.scrollToPositionWithOffset()方法。
這個方法主要就是為了讓這個東西在頂部
![image.png](https://upload-images.jianshu.io/upload_images/8375678-dae40ad734063a90.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


這里提到了scrollToPositionWithOffset()方法,就順便說一下scrollToPosition和scrollToPositionWithOffset的區別

scrollToPosition 會把不在屏幕的 Item 移動到屏幕上,原來在上方的 Item 移動到 可見 Item 的第一項,在下方的移動到屏幕可見 Item 的最后一項。已經顯示的 Item 不會移動。

scrollToPositionWithOffset 會把 Item 移動到可見 Item 的第一項,即使它已經在可見 Item 之中。另外它還有 offset 參數,表示 Item 移動到第一項后跟 RecyclerView 上邊界或下邊界之間的距離(默認是 0) 

要實現這個效果還就得使用scrollToPositionWithOffset()這個方法,我也沒有重寫一個Manager,就這樣直接使用了

## 使用到的數據結構
![image.png](https://upload-images.jianshu.io/upload_images/8375678-9d1d5fe62233c86f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
就是一個簡單的省市結構類型
這里我也把JSON放上來了
[{"productEntities":[{"parentId":"1","productCartMoney":0.0,"productCount":0,"productId":"1","productImg":"img地址","productMoney":10.0,"productMonth":"34","productName":"新上市獼猴桃1-1"},{"parentId":"1","productCartMoney":0.0,"productCount":0,"productId":"2","productImg":"img地址","productMoney":20.0,"productMonth":"34","productName":"新上市獼猴桃2-1"},{"parentId":"1","productCartMoney":0.0,"productCount":0,"productId":"3","productImg":"img地址","productMoney":30.0,"productMonth":"34","productName":"新上市獼猴桃3-1"},{"parentId":"1","productCartMoney":0.0,"productCount":0,"productId":"4","productImg":"img地址","productMoney":40.0,"productMonth":"34","productName":"新上市獼猴桃4-1"},{"parentId":"1","productCartMoney":0.0,"productCount":0,"productId":"5","productImg":"img地址","productMoney":50.0,"productMonth":"34","productName":"新上市獼猴桃5-1"},{"parentId":"1","productCartMoney":0.0,"productCount":0,"productId":"6","productImg":"img地址","productMoney":50.0,"productMonth":"34","productName":"新上市獼猴桃6-1"},{"parentId":"1","productCartMoney":0.0,"productCount":0,"productId":"7","productImg":"img地址","productMoney":50.0,"productMonth":"34","productName":"新上市獼猴桃7-1"}],"typeCount":0,"typeId":"1","typeName":"熱銷水果"}]

數據源准備完之后就開始下面的實現了

###設置adapter
```
//設置數據源,數據綁定展示
leftAdapter = new LeftProductTypeAdapter(MainActivity.this, productListEntities);
rightAdapter = new RightProductAdapter(MainActivity.this, productListEntities, shopCart);


rightMenu.setAdapter(rightAdapter);
leftMenu.setAdapter(leftAdapter);
//左側列表單項選擇
leftAdapter.addItemSelectedListener(this);
rightAdapter.setShopCartImp(this);
//設置初始頭部
initHeadView();
```
剛才從圖上也看到了右側列表又一個標題,這個標題就是為了占位和顯示使用
```
/**
* 初始頭部
*/
private void initHeadView() {
headMenu = rightAdapter.getMenuOfMenuByPosition(0);
headerLayout.setContentDescription("0");
headerView.setText(headMenu.getTypeName());
}
```
別忘了設置LayoutManager,這里為什么使用LinearLayoutManager在上邊也說了,主要是使用LinearLayoutManager的scrollToPositionWithOffset()方法
```
leftMenu.setLayoutManager(new LinearLayoutManager(this));
rightMenu.setLayoutManager(new LinearLayoutManager(this));
```
數據綁定之后,就是列表的滑動了,先說右側列表數據滑動,然后讓左側選中
###右側列表滑動監聽
```
rightMenu.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}

@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (recyclerView.canScrollVertically(1) == false) {//無法下滑
showHeadView();
return;
}

View underView = null;
if (dy > 0) {
underView = rightMenu.findChildViewUnder(headerLayout.getX(), headerLayout.getMeasuredHeight() + 1);
} else {
underView = rightMenu.findChildViewUnder(headerLayout.getX(), 0);
}

if (underView != null && underView.getContentDescription() != null) {
int position = Integer.parseInt(underView.getContentDescription().toString());
ProductListEntity menu = rightAdapter.getMenuOfMenuByPosition(position);

if (leftClickType || !menu.getTypeName().equals(headMenu.getTypeName())) {
if (dy > 0 && headerLayout.getTranslationY() <= 1 && headerLayout.getTranslationY() >= -1 * headerLayout.getMeasuredHeight() * 4 / 5 && !leftClickType) {// underView.getTop()>9
int dealtY = underView.getTop() - headerLayout.getMeasuredHeight();
headerLayout.setTranslationY(dealtY);
} else if (dy < 0 && headerLayout.getTranslationY() <= 0 && !leftClickType) {
headerView.setText(menu.getTypeName());
int dealtY = underView.getBottom() - headerLayout.getMeasuredHeight();
headerLayout.setTranslationY(dealtY);
} else {
headerLayout.setTranslationY(0);
headMenu = menu;
headerView.setText(headMenu.getTypeName());
for (int i = 0; i < productListEntities.size(); i++) {
if (productListEntities.get(i) == headMenu) {
leftAdapter.setSelectedNum(i);
break;
}
}
if (leftClickType) leftClickType = false;
}
}
}

}
});
```

```
private void showHeadView() {
headerLayout.setTranslationY(0);
View underView = rightMenu.findChildViewUnder(headerLayout.getX(), 0);
if (underView != null && underView.getContentDescription() != null) {
int position = Integer.parseInt(underView.getContentDescription().toString());
ProductListEntity entity = rightAdapter.getMenuOfMenuByPosition(position + 1);
headMenu = entity;
headerView.setText(headMenu.getTypeName());
for (int i = 0; i < productListEntities.size(); i++) {
if (productListEntities.get(i) == headMenu) {
leftAdapter.setSelectedNum(i);
break;
}
}
}
}
```
以上代碼就是兩個列表滑動交互右側的主要代碼,以上實現的是右側滑動分組置頂的效果
接下來就是
###右側滑動,左側選中
選中的主要代碼是LeftProductTypeAdapter中的setSelectedNum();
```
/**
* 選中左側區域
*
* @param selectedNum
*/
public void setSelectedNum(int selectedNum) {
if (selectedNum < getItemCount() && selectedNum >= 0) {
this.mSelectedNum = selectedNum;
notifyDataSetChanged();
}
}
```
然后adapter中設置選中的樣式就可以
```
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ProductListEntity dishMenu = mMenuList.get(position);
LeftMenuViewHolder viewHolder = (LeftMenuViewHolder) holder;
viewHolder.menuName.setText(dishMenu.getTypeName());
if (mSelectedNum == position) {
viewHolder.menuLayout.setSelected(true);
} else {
viewHolder.menuLayout.setSelected(false);
}
```
說完了右側滑動讓左側選中,那么接下來就是左側點擊讓右側對應的分組顯示出來
###左側列表點擊,右側分組顯示在頂部
在LeftProductTypeAdapter中暴露接口
```
public interface onItemSelectedListener {
public void onLeftItemSelected(int postion, ProductListEntity menu);
}

public void addItemSelectedListener(onItemSelectedListener listener) {
if (mSelectedListenerList != null)
mSelectedListenerList.add(listener);
}
```
在Activity中實現,這里直接使用scrollToPositionWithOffset方法就可以了,相對來說比較簡單
```
/**
* 左側列表單項選中
*
* @param position
* @param menu
*/
@Override
public void onLeftItemSelected(int position, ProductListEntity menu) {
int sum = 0;
for (int i = 0; i < position; i++) {
sum += productListEntities.get(i).getProductEntities().size() + 1;
}
// StickyHeaderLayoutManager layoutManager = (StickyHeaderLayoutManager) rightMenu.getLayoutManager();
LinearLayoutManager layoutManager = (LinearLayoutManager) rightMenu.getLayoutManager();
rightMenu.scrollToPosition(position);
layoutManager.scrollToPositionWithOffset(sum, 0);
leftClickType = true;
}
```
目前為至,左右兩個列表就可以實現交互了,左側點擊右側顯示指定數據,右側滑動左側選中對應的內容
列表的聯動處理完成,接下來就是右側列表商品加減操作了
###右側列表加減處理
右側列表的加減操作這里沒有暴露到Activity中操作,是在Adapter中設置的
####RightProductAdapter
```
//加減點擊時間
dishholder.iv_group_list_item_count_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e("posss", "-------------------posss:" + posss);
if (shopCart.addShoppingSingle(dish)) {
// notifyItemChanged(position);
//當前數字變化刷新
notifyDataSetChanged();
if (shopCartImp != null) {
shopCartImp.add(view, position,dish);

}
}
}
});

dishholder.iv_group_list_item_count_reduce.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (shopCart.subShoppingSingle(dish)) {
// notifyItemChanged(position);
//當前數字變化刷新
notifyDataSetChanged();
if (shopCartImp != null)
shopCartImp.remove(view, position,dish);

}
}
});
```
這里沒有使用notifyItemChanged()方法刷新,因為這個方法,完成操作之后刷新,商品圖片有閃爍效果,所以這里使用的notifyDataSetChanged()方法替代

在上邊adapter中的處理中,我們看到了shopCart.addShoppingSingle(dish) 的判讀
這里的shopCart 是一個購物車實體,可以理解為 中轉處
看下ShopCart的內容
```
public class ShopCart {
private int shoppingAccount;//數量
private double shoppingTotalPrice;//購物車總價格
private Map<ProductListEntity.ProductEntity, Integer> shoppingSingle;//保存數量
private Map<String, Integer> parentCountMap;//父保存數量


public ShopCart() {
this.shoppingAccount = 0;
this.shoppingTotalPrice = 0.0;
this.shoppingSingle = new HashMap<>();
this.parentCountMap = new HashMap<>();
}

public boolean addShoppingSingle(ProductListEntity.ProductEntity dish) {
double remain = dish.getProductCartMoney();
// if (remain <= 0)
// return false;
//商品的價格,減操作直接--
dish.setProductCartMoney(--remain);
int num = 0;
if (shoppingSingle.containsKey(dish)) {
num = shoppingSingle.get(dish);
}
num += 1;
/***/
dish.setProductCount(num);
shoppingSingle.put(dish, num);

//如果這個map存在這個父ID的值
int parentNum = 0;
if (parentCountMap.containsKey(dish.getParentId())) {
parentNum = parentCountMap.get(dish.getParentId());
parentNum += 1;
} else {//如果第一次存儲
parentNum = 1;
}
parentCountMap.put(dish.getParentId(), parentNum);

Log.e("TAG", "addShoppingSingle: " + shoppingSingle.get(dish));
shoppingTotalPrice += dish.getProductMoney();//加商品的正常價格
shoppingAccount += num;
return true;
}

public boolean subShoppingSingle(ProductListEntity.ProductEntity dish) {
int num = 0;
if (shoppingSingle.containsKey(dish)) {
num = shoppingSingle.get(dish);
}
if (num <= 0) return false;
num--;
double remain = dish.getProductCartMoney();
dish.setProductCartMoney(++remain);
dish.setProductCount(num);
shoppingSingle.put(dish, num);
if (num == 0) {
shoppingSingle.remove(dish);
}

//如果這個map存在這個父ID的值
int parentNum = 0;
if (parentCountMap.containsKey(dish.getParentId())) {
parentNum = parentCountMap.get(dish.getParentId());
parentNum -= 1;
parentCountMap.put(dish.getParentId(), parentNum);
}
shoppingTotalPrice -= dish.getProductMoney();
shoppingAccount -= num;
return true;
}


public int getShoppingAccount() {
return shoppingSingle.size();
}

public void setShoppingAccount(int shoppingAccount) {
this.shoppingAccount = shoppingAccount;
}

public double getShoppingTotalPrice() {
return shoppingTotalPrice;
}

public void setShoppingTotalPrice(double shoppingTotalPrice) {
this.shoppingTotalPrice = shoppingTotalPrice;
}

public Map<ProductListEntity.ProductEntity, Integer> getShoppingSingle() {
return shoppingSingle;
}

public void setShoppingSingle(Map<ProductListEntity.ProductEntity, Integer> shoppingSingle) {
this.shoppingSingle = shoppingSingle;
}

public Map<String, Integer> getParentCountMap() {
return parentCountMap;
}

public void setParentCountMap(Map<String, Integer> parentCountMap) {
this.parentCountMap = parentCountMap;
}

public void clear() {
this.shoppingAccount = 0;
this.shoppingTotalPrice = 0;
this.shoppingSingle.clear();
}
```
說了點擊在adapter中實現,但是還有加入動畫,所以還是暴露接口給Activity使用
###ShopCartImp
```
public interface ShopCartImp {
void add(View view, int postion, ProductListEntity.ProductEntity entity);

void remove(View view, int postion, ProductListEntity.ProductEntity entity);
}
```
###右側列表加+
由於只有在加的時候才有動畫效果,只有只給add設置addCart動畫效果,加只需要注意是不是第一次添加就可以,如果是第一次添加,ShopCart中已經判斷了,第一次直接put,否則就只改變count就可以了
```
/**
* 購物車+
*
* @param view
* @param position
*/
@Override
public void add(View view, int position, ProductListEntity.ProductEntity entity) {
addCart(view, entity);
}
```

###加入購物車動畫方法
```
//加入購物車曲線動畫
private void addCart(View view, ProductListEntity.ProductEntity entity) {
// 一、創造出執行動畫的主題---imageview
//代碼new一個imageview,圖片資源是上面的imageview的圖片
// (這個圖片就是執行動畫的圖片,從開始位置出發,經過一個拋物線(貝塞爾曲線),移動到購物車里)
final ImageView goods = new ImageView(MainActivity.this);
goods.setImageDrawable(getResources().getDrawable(R.drawable.shape_shopping_cart_num_bg, null));
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(50, 50);
rl.addView(goods, params);

// 二、計算動畫開始/結束點的坐標的准備工作
//得到父布局的起始點坐標(用於輔助計算動畫開始/結束時的點的坐標)
int[] parentLocation = new int[2];
rl.getLocationInWindow(parentLocation);

//得到商品圖片的坐標(用於計算動畫開始的坐標)
int startLoc[] = new int[2];
view.getLocationInWindow(startLoc);

//得到購物車圖片的坐標(用於計算動畫結束后的坐標)
int endLoc[] = new int[2];
iv_shopping_cart_img.getLocationInWindow(endLoc);


// 三、正式開始計算動畫開始/結束的坐標
//開始掉落的商品的起始點:商品起始點-父布局起始點+該商品圖片的一半
float startX = startLoc[0] - parentLocation[0] + goods.getWidth() / 2;
float startY = startLoc[1] - parentLocation[1] + goods.getHeight() / 2;

//商品掉落后的終點坐標:購物車起始點-父布局起始點+購物車圖片的1/5
float toX = endLoc[0] - parentLocation[0] + iv_shopping_cart_img.getWidth() / 5;
float toY = endLoc[1] - parentLocation[1];

// 四、計算中間動畫的插值坐標(貝塞爾曲線)(其實就是用貝塞爾曲線來完成起終點的過程)
//開始繪制貝塞爾曲線
Path path = new Path();
//移動到起始點(貝塞爾曲線的起點)
path.moveTo(startX, startY);
//使用二次薩貝爾曲線:注意第一個起始坐標越大,貝塞爾曲線的橫向距離就會越大,一般按照下面的式子取即可
path.quadTo((startX + toX) / 2, startY, toX, toY);
//mPathMeasure用來計算貝塞爾曲線的曲線長度和貝塞爾曲線中間插值的坐標,
// 如果是true,path會形成一個閉環
mPathMeasure = new PathMeasure(path, false);

//★★★屬性動畫實現(從0到貝塞爾曲線的長度之間進行插值計算,獲取中間過程的距離值)
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
valueAnimator.setDuration(500);
// 勻速線性插值器
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 當插值計算進行時,獲取中間的每個值,
// 這里這個值是中間過程中的曲線長度(下面根據這個值來得出中間點的坐標值)
float value = (Float) animation.getAnimatedValue();
// ★★★★★獲取當前點坐標封裝到mCurrentPosition
// boolean getPosTan(float distance, float[] pos, float[] tan) :
// 傳入一個距離distance(0<=distance<=getLength()),然后會計算當前距
// 離的坐標點和切線,pos會自動填充上坐標,這個方法很重要。
mPathMeasure.getPosTan(value, mCurrentPosition, null);//mCurrentPosition此時就是中間距離點的坐標值
// 移動的商品圖片(動畫圖片)的坐標設置為該中間點的坐標
goods.setTranslationX(mCurrentPosition[0]);
goods.setTranslationY(mCurrentPosition[1]);
}
});
// 五、 開始執行動畫
valueAnimator.start();

// 六、動畫結束后的處理
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {

}

//當動畫結束后:
@Override
public void onAnimationEnd(Animator animation) {
//更新底部數據
showTotalPrice(entity);
// 把移動的圖片imageview從父布局里移除
rl.removeView(goods);
}

@Override
public void onAnimationCancel(Animator animation) {

}

@Override
public void onAnimationRepeat(Animator animation) {

}
});
}
```
###右側列表減-
```
/**
* 購物車減
*
* @param view
* @param position
*/
@Override
public void remove(View view, int position, ProductListEntity.ProductEntity en) {
showTotalPrice(en);
}
```
###價格展示更新
```
/**
* 底部價格和數量顯示
*/
private void showTotalPrice(ProductListEntity.ProductEntity entity) {
if (shopCart != null && shopCart.getShoppingTotalPrice() > 0) {
tv_shopping_cart_money.setVisibility(View.VISIBLE);
tv_shopping_cart_money.setText("¥ " + shopCart.getShoppingTotalPrice());
tv_shopping_cart_count.setVisibility(View.VISIBLE);
//得到總的數量
int textCount = 0;
for (ProductListEntity.ProductEntity m : shopCart.getShoppingSingle().keySet()) {
Log.e("btn_shopping_cart_pay", "map集合中存儲的數據---->" + m.getProductCount());
textCount += m.getProductCount();
}
tv_shopping_cart_count.setText("" + textCount);
} else {
tv_shopping_cart_money.setVisibility(View.INVISIBLE);
tv_shopping_cart_count.setVisibility(View.GONE);
}
updateLeftCount(entity);
}
```
##到了這里右側商品列表加和減還有加的動畫效果就完成了,接下來就是右側增加或者減少,怎么來改變左側的角標顯示
左側角標改變方法這里要注意的是
#注意
有人可以在上邊數據結構商品的對象中看到
#ParentId
這個字段,有人可能會問這個有需要嗎,但是這個ID確實在這里用到了,當然可能也有其他的實現方法可能不需要這個字段,這里是通過子項中的父級ID和左側列表中的ID來進行比對的,比對一致則說明我操作的數據屬於左側這一組中
```
/**
* 更新左側數字角標(暫時不包含清空),觸發更新肯定是在加或者減的時候觸發,根據子項中的父ID和左側ID比對,
*/
private void updateLeftCount(ProductListEntity.ProductEntity entity) {
if (shopCart != null) {
//加和減的時候要知道是那個左側下邊的,知道下標獲取父id,然后從map中取count
if (entity != null) {
Log.e("updateLeftCount", "-------parentId:" + entity.getParentId() + "---------count:" + shopCart.getParentCountMap().get(entity.getParentId()));
leftAdapter.setUpdateMenuCount(entity.getParentId(), shopCart.getParentCountMap().get(entity.getParentId()));
}
if (rightAdapter != null) rightAdapter.notifyDataSetChanged();//跟新列表
}
}
```

###LeftProductTypeAdapter中設置setUpdateMenuCount()方法
```
/**
* 更新左側角標,需要知道那個對象
*
* @param
*/
public void setUpdateMenuCount(String parentId, int mUpdateParentCount) {
//需要實體數據保存更新
this.mUpdateParentId = parentId;
this.mUpdateParentCount = mUpdateParentCount;
notifyDataSetChanged();
this.clearCount = false;

}
```
同時在onBindViewHolder中判斷設置大於0才顯示
```
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ProductListEntity dishMenu = mMenuList.get(position);
LeftMenuViewHolder viewHolder = (LeftMenuViewHolder) holder;
viewHolder.menuName.setText(dishMenu.getTypeName());
if (mSelectedNum == position) {
viewHolder.menuLayout.setSelected(true);
} else {
viewHolder.menuLayout.setSelected(false);
}

if (dishMenu.getTypeId().equals(mUpdateParentId)) {//選中的ID
//更改數據
dishMenu.setTypeCount(mUpdateParentCount);
}
if (clearCount) {//隱藏所有數據,設置count都為0
viewHolder.tv_left_menu_count.setVisibility(View.GONE);
dishMenu.setTypeCount(0);
} else {
viewHolder.tv_left_menu_count.setVisibility(View.VISIBLE);
viewHolder.tv_left_menu_count.setText(dishMenu.getTypeCount() + "");
dishMenu.setTypeCount(dishMenu.getTypeCount());
}
if (dishMenu.getTypeCount() > 0) {//展示
viewHolder.tv_left_menu_count.setVisibility(View.VISIBLE);
viewHolder.tv_left_menu_count.setText(dishMenu.getTypeCount() + "");
} else {//隱藏
viewHolder.tv_left_menu_count.setVisibility(View.GONE);
}


}
```
到這里右側加減操作,左側角標也對應變化展示了,最后就是購物車彈窗
###展示底部購物車
展示底部購物車這里使用了XPopup,可以自己引入,也可以引我項目中的poplibrary庫,和meituanshoppingcart同級了
###底部購物車展示
上面說的顯示當前屏幕的百分之七十高度就是在這里設置的
```
Log.e("getWindowHeight", "---------height:" + Tool.getWindowHeight(MainActivity.this));
//獲取屏幕的高度,然后拿到百分之70
int popHeight = (int) (Tool.getWindowHeight(MainActivity.this) * 0.7);
if (shopCart != null && shopCart.getShoppingAccount() > 0) {
new XPopup.Builder(MainActivity.this)
.atView(view)
.maxHeight(popHeight)
.isRequestFocus(false)
.asCustom(new CustomPartShadowPopupView(MainActivity.this, shopCart))
.show();
}
```
###CustomPartShadowPopupView
```
/**
* @className: CustomPartShadowPopupView
* @description:
* @author: dingchao
* @time: 2020-11-19 15:13
*/
public class CustomPartShadowPopupView extends PartShadowPopupView implements ShopCartImp, View.OnClickListener {
private ListView lv_pop_list;
private Context context;
private ShopCart shopCart;
private TextView tv_shopping_cart_clear_all;
private TextView tv_shopping_cart_top_key_v;
ShoppingCartAdapter shoppingCartAdapter;

public CustomPartShadowPopupView(@NonNull Context context, ShopCart shopCart) {
super(context);
this.context = context;
this.shopCart = shopCart;
}

@Override
protected int getImplLayoutId() {
return R.layout.pop_shopping_cart;
}

@Override
protected void onCreate() {
super.onCreate();
initListener();
initDataViewBind();
}

/**
* 控件初始綁定
*/
private void initListener() {
lv_pop_list = findViewById(R.id.lv_pop_list);
tv_shopping_cart_clear_all = findViewById(R.id.tv_shopping_cart_clear_all);
tv_shopping_cart_top_key_v = findViewById(R.id.tv_shopping_cart_top_key_v);
tv_shopping_cart_clear_all.setOnClickListener(this);
}


/**
* 初始數據綁定及操作
*/
private void initDataViewBind() {
//數據綁定及展示
shoppingCartAdapter = new ShoppingCartAdapter(context, shopCart);
lv_pop_list.setAdapter(shoppingCartAdapter);
shoppingCartAdapter.setShopCartImp(this);
updateShoppingCartNum();
}

@Override
protected void onShow() {
super.onShow();
}

@Override
protected void onDismiss() {
super.onDismiss();
}

@Override
public void add(View view, int postion, ProductListEntity.ProductEntity entity) {
updateShoppingCartNum();
EventBus.getDefault().post(new EventBusShoppingEntity(entity, "add"));
}

/**
* 更新數字
*/
private void updateShoppingCartNum() {
if (shopCart != null) {
int textCount = 0;
for (ProductListEntity.ProductEntity m : shopCart.getShoppingSingle().keySet()) {
Log.e("btn_shopping_cart_pay", "map集合中存儲的數據---->" + m.getProductCount());
textCount += m.getProductCount();
}
tv_shopping_cart_top_key_v.setText("(共" + textCount + "件商品)");
}
}

@Override
public void remove(View view, int postion, ProductListEntity.ProductEntity entity) {
//判讀count是不是到0了,到0說明沒數據了,如果購物車彈窗開着,則關閉
updateShoppingCartNum();
EventBus.getDefault().post(new EventBusShoppingEntity(entity, "reduce"));
if (shopCart != null && shopCart.getShoppingAccount() == 0) {
this.dismiss();
}
}

@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.tv_shopping_cart_clear_all:
//清空
shopCart.clear();
this.dismiss();
updateShoppingCartNum();
EventBus.getDefault().post(new EventBusShoppingEntity(null, "clearAll"));
break;
default:
break;
}
}
}
```
這里使用了EventBus來進行通知Activity來通知更新右側列表數量和左側列表的角標更新
對應的接受方法
```
//定義處理接收的方法
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(EventBusShoppingEntity entity) {
if (entity.getKey().equals("add")) {
showTotalPrice(entity.getEntity());
} else if (entity.getKey().equals("reduce")) {
showTotalPrice(entity.getEntity());
} else if (entity.getKey().equals("clearAll")) {//清空全部
clearCartDataAndListData();
}
}

```
最后就是清空數據和提交時提交的數據
###清空
```
/**
* 清空購物車及左側列表都角標和商品列表
*/
private void clearCartDataAndListData() {
shopCart.clear();
shopCart.getParentCountMap().clear();
showTotalPrice(null);
//左側清空
leftAdapter.setClearCount();
}
```
###提交
```
//結算的商品列表
ToastUtil.showShort(MainActivity.this, "dianjile");
if (shopCart.getShoppingSingle().size() > 0) {
List<ProductListEntity.ProductEntity> commitListData = new ArrayList<>();
for (ProductListEntity.ProductEntity m : shopCart.getShoppingSingle().keySet()) {
Log.e("btn_cart_pay", "map集合中存儲的數據---->" + m.getProductCount());
commitListData.add(m);
}
for (int i = 0; i < commitListData.size(); i++) {
Log.e("btn_cart_pay_list", "commitList---->" + commitListData.get(i));
}
Log.e("btn_cart_pay_list_JSON", "commitList---->" + JSON.toJSONString(commitListData));
}

```

這里提交的數據其實就是改變了count的對象,由於后台要快照,所以會要求我們給傳遞數據,所以咋回來的,咋在給他們就完了,只把最后的數量更改提交就可以了。
###提交的例子
```
[{
"parentId": "1",
"productCount": 2,
"productId": "1",
"productImg": "img地址",
"productMoney": 10.0,
"productMonth": "34",
"productName": "新上市獼猴桃1-1"
}, {
"parentId": "1",
"productCount": 1,
"productId": "4",
"productImg": "img地址",
"productMoney": 40.0,
"productMonth": "34",
"productName": "新上市獼猴桃4-1"
}]
```
以上就是仿美團雙列表添加購物車交互的效果的大體代碼和邏輯,說的可能比較亂,需要完整代碼的可以去上面地址找
###meituanshoppingcart
項目自己下載一下,代碼挺簡單的,注釋也寫了不算少,挺好看懂的,如果有這種效果更好的實現思路和方法的也歡迎各位大神指教,共同進步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM