购物车完整效果(上)


看到 饿了么美团 的添加商品到购物车的效果,一直觉得很不错,虽然网上有很多博客已经实现了相似的效果,但是好像都没有那么全面,然而用到自己的项目中,也并没有那么实用。在此,系统的整理的一下,争取全面实用些!希望对于爱学习的你,也有所帮助。

话不多说,先贴出效果图:
在这里插入图片描述

那我们就从效果图,入手分析一下吧:实现如上效果,我们可以分以下几步:

  1. 左右列表的联动 (重点)
  2. 右边列表标题的悬停效果(粘性标签)
  3. 添加和减少商品时,按钮的动画
  4. 添加商品时的抛物线动画
  5. 底部弹出购物车清单
  6. 底部购物车清单列表和右边商品列表的联动 (重点)

下面,我会带大家逐一实现上面的每一步,并争取每一步都有多种实现方案。

由于整篇博客,侧重于思路,只会贴出关键代码,建议大家可一边看 源码 一边看博客。

左右列表的联动

说到左右列表联动,首先需要说明一下,这里左边是RecyclerView,右边也是RecyclerView。
(1)第一步,我们左边列表,点击每一项,都要有选中的效果。这里我们可以用一个 boolean变量来实现,如果点击了某个item,遍历列表时,就给相应item对象中的变量赋值为true,其他赋值为false,然后刷新adapter即可。
关键代码:

/**
     * 更新左边菜单类型的状态
     *
     * @param position
     */
    private void updateMenuStatus(int position) {
        List<LeftResult> dataList = mLeftAdapter.getData();
        for (int i = 0; i < dataList.size(); i++) {
            if (i == position) {
                dataList.get(i).isSelect = true;
            } else {
                dataList.get(i).isSelect = false;
            }
        }
        mLeftAdapter.notifyDataSetChanged();
    }

(2)那左边列表选中某一项时,右边列表如果滑动到相应类别项呢?我们可以使用LinearLayoutManager中的mLayoutManager.scrollToPositionWithOffset(0, 0);这个属性,就能轻松实现啦。
关键代码:

  mLeftAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
                updateMenuStatus(position);//更新左边菜单类型的状态
                if (position == 0) {
                    //mLayoutManager.scrollToPosition(0);//测试发现这个,方法是不好的。
                    mLayoutManager.scrollToPositionWithOffset(0, 0);
                } else {
                    if (productBeanList != null && productBeanList.size() > 0) {
                        int scrollToPosition = 0;
                        for (int i = 0; i < position; i++) {
                            scrollToPosition += productBeanList.get(i).list.size() + 1;
                        }
                        Log.i(TAG, "onItemClick: scrollToPosition=" + scrollToPosition);
                        mLayoutManager.scrollToPositionWithOffset(scrollToPosition, 0);
                    }
                }
            }
        });

(3)下面这一步我们主要来实现,当右边列表滑动时,滑动到某一类别时,左边列表同时也切换到相同类别项。那如何实现呢?好,我想想看,,,,对,你想的没错,我们肯定要监听RecyclerView的滚动事件啦。
关键代码:

 mContentRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == 0) {//解决滑动过快,firstVisibleItemPosition 没有被赋值,导致左边菜单项,状态设置不对的问题。
                    int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
                    String strTitle = mContentResultList.get(firstVisibleItemPosition).getTitle();
                    Log.i(TAG, "onScrollStateChanged: firstVisibleItemPosition=" + firstVisibleItemPosition);
                    for (LeftResult leftResult : mLeftResultList) {
                        if (leftResult.name.equals(strTitle)) {
                            leftResult.isSelect = true;
                        } else {
                            leftResult.isSelect = false;
                        }
                    }
                    mLeftAdapter.notifyDataSetChanged();
                }
            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();


                String strTitle = mContentResultList.get(firstVisibleItemPosition).getTitle();
                String strName = mContentResultList.get(firstVisibleItemPosition).getName();

                if (TextUtils.isEmpty(strName) && !TextUtils.isEmpty(strTitle)) {
                    //Log.i(TAG, "onScrolled: strTitle=" + strTitle + " firstVisibleItemPosition=" + firstVisibleItemPosition);

                    for (LeftResult leftResult : mLeftResultList) {
                        if (leftResult.name.equals(strTitle)) {
                            leftResult.isSelect = true;
                        } else {
                            leftResult.isSelect = false;
                        }
                    }
                    mLeftAdapter.notifyDataSetChanged();
                }

                //悬浮标题处理 实现
                hoverTitleDealWith(recyclerView, firstVisibleItemPosition);


            }
        });

ps: 你可能对 if (TextUtils.isEmpty(strName) && !TextUtils.isEmpty(strTitle)) {…} 这样的判断,不太明白,觉得这里也很有必要说明一下。其实上面所说的功能,关键思路是:在滑动时,实时获取顶部第一个可见的item,判断这个 item 是否为 类别 item,是的话,就遍历左边的列表比对,符合的就赋值为true,然后刷新adapter即可。 判断这个 item 是否为 类别 item 的方法应该有很多种,我这里用的 比对 item 对象中某些字段值。

这时,也许你可能会有些明白啦,是的,类别item项的title肯定是有值的,而非类别item项的title是没有被赋值的。那这一个字段判断,不就行了吗,为什么还要有一个字段呢?当你用这一个字段的时候,理论上是没有问题的,可很奇怪的是,当你快速滑动列表时,你会发现 firstVisibleItemPosition 有可能会赋不上值。这样就会导致左边菜单项,状态设置不对。

那怎么解决呢?代码上面已经有了,思路是:在列表滚动结束时,判断 第一个可见的item属于哪个类别项,是哪一个,左边的类别项,就更新哪一个的状态。因此这里,不得不在初始化右边列表数据时,给每个列表项,都赋值一个类别title。所以 if (TextUtils.isEmpty(strName) && !TextUtils.isEmpty(strTitle)) {…} 这里要加两个判断才可以。商品名为空,并且 类别title不为空,才肯定是类别item。

右边列表标题的悬停效果(粘性标签)

这里,我们来实现,右边标题悬停的效果。这里网上也有很多开源的框架,你在github上搜类似这样的关键词 StickyHeaders,StickyListHeaders 会有不少。不过这里,推荐一下这个吧:PinnedSectionItemDecoration 。使用起来也比较简单(用法在 源码 中,点击商品列表,进入的界面有展示),关键是如果你右边的列表用的是recycleview,用这个就可以。有的开源框架,是基于 listView 的封装,你用 recycleview 就不行了。

那如果想自己写个呢,该如何实现呢?其实上面效果图中,并没有使用开源框架,而是结合自己的理解写的,效果也是挺不错的吧。下面看看具体思路和关键代码:

/**
     * 悬浮标题处理
     * <p>
     * 实现步骤描述:
     * (1):要获取到列表中,第一个显示的item+1 的item的bean类,判断是否为菜单类型
     * 是的话,把该item的 position 赋值给一个临时变量 a
     * (2):在滑动过程中,当 临时变量 a  等于 列表中第一个显示的item+1 的时候,
     * (3):获取列表中( View childView = recyclerView.getChildAt(0);)第一个view, 通过  childView.getBottom()
     * 实时获取item显示的距离
     * <p>
     * (4):通过比较 item显示的距离 和 悬浮标题的高度,来动态设置 悬浮标题 移动 即可!
     *
     * @param recyclerView
     * @param firstVisibleItemPosition
     */
    private void hoverTitleDealWith(@NonNull RecyclerView recyclerView, int firstVisibleItemPosition) {

        //int section = getSectionForPosition(firstVisibleItemPosition);
        //int nextSection = getSectionForPosition(firstVisibleItemPosition + 1);
        //int nextSecPosition = getPositionForSection(+nextSection);

        int nextSection = 0;//下一个位置
        String strNextTitle = mContentResultList.get(firstVisibleItemPosition + 1).getTitle();
        String strNextName = mContentResultList.get(firstVisibleItemPosition + 1).getName();
        if (TextUtils.isEmpty(strNextName) && !TextUtils.isEmpty(strNextTitle)) {
            nextSection = firstVisibleItemPosition + 1;
        }

        //Log.i(TAG, "onScrolled: firstVisibleItemPosition=" + firstVisibleItemPosition + " lastFirstVisibleItem=" + lastFirstVisibleItem);
        if (firstVisibleItemPosition != lastFirstVisibleItem) {
            ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) tvHoverTitle.getLayoutParams();
            params.topMargin = 0;
            tvHoverTitle.setLayoutParams(params);

            tvHoverTitle.setText(mContentResultList.get(firstVisibleItemPosition).getTitle());

        }

        //Log.i(TAG, "onScrolled: nextSection="+nextSection+" nextSecPosition="+nextSecPosition);
        if (nextSection == firstVisibleItemPosition + 1) {
            View childView = recyclerView.getChildAt(0);
            if (childView != null) {
                int titleHeight = tvHoverTitle.getHeight();

                int bottom = childView.getBottom();//我认为这行代码,很关键。(获取顶部item 显示出来的高度)

                ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) tvHoverTitle.getLayoutParams();

                //Log.i(TAG, "onScrolled: bottom=" + bottom + " titleHeight=" + titleHeight);
                if (bottom < titleHeight) {

                    float pushedDistance = bottom - titleHeight;
                    params.topMargin = (int) pushedDistance;
                    tvHoverTitle.setLayoutParams(params);
                } else {
                    if (params.topMargin != 0) {
                        params.topMargin = 0;
                        tvHoverTitle.setLayoutParams(params);
                    }
                }
            }
        }
        lastFirstVisibleItem = firstVisibleItemPosition;
    }

ps:代码中都已经说的比较明白了,这里就不在废话了。

添加和减少商品时,按钮的动画

这里,效果图中,用的是这个开源库:AnimShopButton 挺实用。用法挺简单,关键代码:

<com.example.shopcatdemo.view.shopcart.AnimShopButton
        android:id="@+id/shopCartButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="@dimen/spacing_10"
        app:addEnableBgColor="#f3593c"
        app:addEnableFgColor="#ffffff"
        app:count="0"
        app:gapBetweenCircle="40dp"
        app:hintBgColor="#f3593c"
        app:hintFgColor="#ffffff"
        app:ignoreHintArea="true"
        app:maxCount="99" />

同样那如果我想自己实现一个,怎么实现呢?
还是先贴出一下效果图吧:
在这里插入图片描述

也不再多说啦,源码 中都有的,关键代码如下:

if (item.getCount() > 0) {
                    tvNumber.setVisibility(View.VISIBLE);
                    imageRemove.setVisibility(View.VISIBLE);
                    tvNumber.setText(item.getCount() + "");
                } else {
                    tvNumber.setVisibility(View.INVISIBLE);
                    imageRemove.setVisibility(View.INVISIBLE);
                    tvNumber.setText("0");

                }

                if (item.getInventory() <= 0) {
                    tvNumber.setVisibility(View.INVISIBLE);
                    imageRemove.setVisibility(View.INVISIBLE);
                }


                //减少
                imageRemove.setOnClickListener(v -> {
                    number = item.getCount();
                    if (number == 0) return;

                    number--;
                    item.setCount(number);
                    tvNumber.setText(number + "");

                    if (number == 0) {
                        tvNumber.setVisibility(View.INVISIBLE);
                        imageRemove.setVisibility(View.INVISIBLE);
                        tvNumber.setAnimation(getHiddenAnimation());
                        imageRemove.setAnimation(getHiddenAnimation());
                    }

                    listener.onDelSuccess(imageAdd, number, helper.getLayoutPosition());
                });

                //添加
                imageAdd.setOnClickListener(v -> {
                    number = item.getCount();

                    if (number < item.getInventory()) {
                        number++;
                        item.setCount(number);
                        tvNumber.setText(number + "");

                        if (number == 1) {
                            tvNumber.setVisibility(View.VISIBLE);
                            imageRemove.setVisibility(View.VISIBLE);
                            tvNumber.setAnimation(getShowAnimation());
                            imageRemove.setAnimation(getShowAnimation());
                        }

                        listener.onAddSuccess(imageAdd, number, helper.getLayoutPosition());
                    } else {
                        Toast.makeText(mContext, "已超出库存数量", Toast.LENGTH_SHORT).show();
                    }

                });

由于篇幅太多,后面的几步实现,见 购物车完整效果(下),这篇博客吧。

购物车完整效果(下)


源码git地址


参考博客:
Android仿外卖购物车的实现

仿饿了么购物车效果(UI效果)

仿饿了么添加购物车效果

AnimShopButton

Android 仿饿了么点餐页面 效果如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值