Android 美团购物车效果

老规矩先上效果图


美团购物车.gif

2020-11-23新增吸顶和Fragment配合


gif (2).gif

GIF图有点不清楚,再上两张截图


Wec1111.jpeg
Wech222.jpeg

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

效果就是gif展示的,效果图有了,还是要用文字介绍下的。
效果就是左右两个列表,左侧列表点击时,右侧的标题自动显示到列表的顶部,标题是悬浮吸顶的,没组的标题固定悬浮在顶部,当右侧列表滑动时,左侧列表自动定位至和左侧相同的分类保持统一,底部的弹出购物车区域,购物车的高度是在屏幕的70%以下是自适应的高度,最大高度是当前屏幕的70%,下边是部分代码和实现思路。

这个效果主要要处理的就是两个RecyclerView 的互相交互和数据处理


image.png

这就是整个页面的主要布局,两个recyclerView

至于右侧又一个热销水果的标题,其实使用StickyHeaderLayoutManager 也可以使用标题吸顶,但是使用这个类的话,在后边左侧点击让右侧显示到顶部的时候会特别难处理,而且StickyHeaderLayoutManager这个Manager里边也没有recyclerView的.scrollToPositionWithOffset()方法。
这个方法主要就是为了让这个东西在顶部


image.png

这里提到了scrollToPositionWithOffset()方法,就顺便说一下scrollToPosition和scrollToPositionWithOffset的区别

scrollToPosition 会把不在屏幕的 Item 移动到屏幕上,原来在上方的 Item 移动到 可见 Item 的第一项,在下方的移动到屏幕可见 Item 的最后一项。已经显示的 Item 不会移动。

scrollToPositionWithOffset 会把 Item 移动到可见 Item 的第一项,即使它已经在可见 Item 之中。另外它还有 offset 参数,表示 Item 移动到第一项后跟 RecyclerView 上边界或下边界之间的距离(默认是 0)

要实现这个效果还就得使用scrollToPositionWithOffset()这个方法,我也没有重写一个Manager,就这样直接使用了

使用到的数据结构

image.png

就是一个简单的省市结构类型
这里我也把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

项目自己下载一下,代码挺简单的,注释也写了不算少,代码还没进行优化处理,效果和基本的思路是这样的,如果有这种效果更好的实现思路和方法的也欢迎各位大神指教,共同进步。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容