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

  • 相关阅读:
    spring boot的gradle整合日志
    eclipse编译器显示的文件名乱码,活动标签乱码(已解决)
    springboot+mybatis+freemark+oauth开发环境搭建
    jQuery自定义alert,confirm方法及样式
    jQuery生成二维码
    input设置背景透明、placeholder的字体颜色及大小
    给tomcat配置外部资源路径(应用场景:web项目访问图片视频等资源)
    tomcat双击startup.bat启动时闪退
    git从远程分支clone项目到本地,切换分支命令,其他常用命令
    java遍历实体类的属性和值
  • 原文地址:https://www.cnblogs.com/dingxiansen/p/14008262.html
Copyright © 2011-2022 走看看