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
项目自己下载一下,代码挺简单的,注释也写了不算少,挺好看懂的,如果有这种效果更好的实现思路和方法的也欢迎各位大神指教,共同进步。

posted @ 2020-11-19 22:10  丁先森  阅读(682)  评论(0编辑  收藏  举报