仿饿了么添加购物车动画

作者: Maybe_G | 来源:发表于2019-01-18 15:44 被阅读19次

原文网址

https://www.jianshu.com/p/b7bacc6805c2

根据以上方法结合自己的框架修改实现效果。

0 效果

效果图

1 Item布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="140dp"
    android:padding="5dp">

    <com.zykj.djs.widget.RoundImageView
        android:id="@+id/iv_good"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:src="@color/colorText"
        app:radius="5dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:text="地浆水入门套装,精华补水提亮肤色"
            android:textColor="@color/colorBlack"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/tv_money"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:text="¥ 186.00"
            android:textColor="@color/colorText"
            android:textSize="20sp" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <TextView
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1" />

            <!--放在加的图标前面-->
            <ImageView
                android:id="@+id/iv_goods_reduce"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignTop="@id/iv_goods_add"
                android:layout_marginLeft="37dp"
                android:layout_toLeftOf="@id/tv_goods_count"
                android:src="@mipmap/one_jianshao"
                android:visibility="visible"/>

            <TextView
                android:id="@+id/tv_goods_count"
                android:layout_width="28dp"
                android:layout_height="24dp"
                android:layout_alignTop="@id/iv_goods_add"
                android:layout_toLeftOf="@id/iv_goods_add"
                android:gravity="center"
                android:textColor="#333333"
                android:textSize="14sp"/>

            <ImageView
                android:id="@+id/iv_goods_add"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:src="@mipmap/one_tianjia"/>



            <TextView
                android:id="@+id/tv_unit"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="10dp"
                android:text="彩妆/瓶" />

        </LinearLayout>

    </LinearLayout>


</LinearLayout>

Item布局效果如下图所示,主要是对加号和减号做动画。


Item布局

2 Adapter写法

public class ProductAdapter extends BaseAdapter<ProductAdapter.ProductHolder, ProductBean> {

    private int reduceLeft = 0;
    private int addLeft = 0;

    private static final long TIME = 300;   // 动画的执行时间

    public ProductAdapter(Context context) {
        super(context);
        setShowFooter(false);
    }

    @Override
    public int provideItemLayoutId() {
        return R.layout.item_product;
    }

    @Override
    public ProductHolder createVH(View view) {
        return new ProductHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ProductHolder holder, int i) {
        holder.mTvTitle.setText(mData.get(i).name);
        holder.mTvMoney.setText(mData.get(i).price);
        holder.mTvGoodsCount.setText(mData.get(i).count == 0 ? "" : String.valueOf(mData.get(i).count));
        holder.mIvGoodsReduce.setVisibility(mData.get(i).count == 0 ? View.INVISIBLE : View.VISIBLE);
    }

    public class ProductHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        @Nullable
        @Bind(R.id.iv_good)
        RoundImageView mIvGood;
        @Nullable
        @Bind(R.id.tv_title)
        TextView mTvTitle;
        @Nullable
        @Bind(R.id.tv_money)
        TextView mTvMoney;
        @Nullable
        @Bind(R.id.iv_goods_reduce)
        ImageView mIvGoodsReduce;
        @Nullable
        @Bind(R.id.tv_goods_count)
        TextView mTvGoodsCount;
        @Nullable
        @Bind(R.id.iv_goods_add)
        ImageView mIvGoodsAdd;


        public ProductHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            mIvGoodsReduce.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    // 获取减少图标的位置
                    reduceLeft = mIvGoodsReduce.getLeft();
                    mIvGoodsReduce.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
            });
            mIvGoodsReduce.setOnClickListener(this);
            mIvGoodsAdd.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    // 获取增加图标的位置
                    addLeft = mIvGoodsAdd.getLeft();
                    mIvGoodsAdd.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
            });
            mIvGoodsAdd.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            if (mOnItemClickListener != null) {
                mOnItemClickListener.onItemClick(view, this.getAdapterPosition());
            }

            switch (view.getId()){
                case R.id.iv_goods_reduce:
                    mData.get(this.getAdapterPosition()).count--;
                    // 防止过快点击出现多个关闭动画
                    if (mData.get(this.getAdapterPosition()).count == 0) {
                        animClose(mIvGoodsReduce);
                        mTvGoodsCount.setText("");
                        // 考虑到用户点击过快
                    } else if (mData.get(this.getAdapterPosition()).count < 0) {
                        // 防止过快点击出现商品数为负数
                        mData.get(this.getAdapterPosition()).count = 0;
                    } else {
                        mTvGoodsCount.setText(String.valueOf(mData.get(this.getAdapterPosition()).count));
                    }
                    break;
                case R.id.iv_goods_add:
                    mData.get(this.getAdapterPosition()).count++;

//                    if (allCount > 0) {
//                        mTvShoppingCartCount.setVisibility(View.VISIBLE);
//                    }
//                    mTvShoppingCartCount.setText(String.valueOf(allCount));
                    if (mData.get(this.getAdapterPosition()).count == 1) {
                        mIvGoodsReduce.setVisibility(View.VISIBLE);
                        animOpen(mIvGoodsReduce);
                    }
                    //addGoods2CartAnim(iv_goods_add);
                    mTvGoodsCount.setText(String.valueOf(mData.get(this.getAdapterPosition()).count));
                    break;
            }
        }
    }

    public void animOpen(final ImageView imageView) {
        AnimatorSet animatorSet = new AnimatorSet();
        ObjectAnimator translationAnim = ObjectAnimator.ofFloat(imageView, "translationX", addLeft - reduceLeft, 0);
        ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(imageView, "rotation", 0, 180);
        animatorSet.play(translationAnim).with(rotationAnim);
        animatorSet.setDuration(TIME).start();
    }

    public void animClose(final ImageView imageView) {
        AnimatorSet animatorSet = new AnimatorSet();
        ObjectAnimator translationAnim = ObjectAnimator.ofFloat(imageView, "translationX", 0, addLeft - reduceLeft);
        ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(imageView, "rotation", 0, 180);
        animatorSet.play(translationAnim).with(rotationAnim);
        animatorSet.setDuration(TIME).start();
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // TODO: 2018/5/19 因为属性动画会改变位置,所以当结束的时候,要回退的到原来的位置,同时用补间动画的位移不好控制
                ObjectAnimator oa = ObjectAnimator.ofFloat(imageView, "translationX", addLeft - reduceLeft, 0);
                oa.setDuration(0);
                oa.start();
                imageView.setVisibility(View.GONE);
            }
        });
    }
}

3 购物车页面布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:id="@+id/snack_layout"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <include layout="@layout/ui_view_toolbar" />


        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycle_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="none"></android.support.v7.widget.RecyclerView>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true">

        <!--这个是下面去结算的页面--!>
        <include layout="@layout/shop_car" />

    </LinearLayout>
</RelativeLayout>

购物车页面布局效果


购物车页面

4 功能实现

public class ProductActivity extends RecycleViewActivity<ListsPresenter, ProductAdapter, ProductBean> {
    @Bind(R.id.snack_layout)
    RelativeLayout mSnackLayout;
    @Bind(R.id.tv_shopping_cart_count)
    TextView mTvShoppingCartCount;
    @Bind(R.id.iv_shopping_cart)
    ImageView iv_shopping_cart;

    private int allCount;


    // 贝塞尔曲线中间过程点坐标
    private float[] mCurrentPosition = new float[2];

    @Override
    protected int provideContentViewId() {
        return R.layout.activity_product;
    }

    @Override
    protected String provideTitle() {
        return "购物车";
    }

    @Override
    protected String provideButton() {
        return null;
    }

    @Override
    protected void initAllMembersView() {
        super.initAllMembersView();

        /*===================自己造的数据 begin====================*/
        List<ProductBean> productBeans = new ArrayList<>();
        ProductBean bean = new ProductBean();
        bean.name = "化妆品1";
        bean.price = "199.00";
        productBeans.add(bean);
        ProductBean bean1 = new ProductBean();
        bean1.name = "化妆品2";
        bean1.price = "99.00";
        productBeans.add(bean1);
        addNews(productBeans, 2);
        /*===================自己造的数据 end====================*/
    }

    @Override
    protected RecyclerView.LayoutManager provideLayoutManager() {
        return new LinearLayoutManager(getContext());
    }

    @Override
    protected ProductAdapter provideAdapter() {
        return new ProductAdapter(getContext());
    }

    @Override
    public ListsPresenter createPresenter() {
        return new ListsPresenter();
    }

    @Override
    public void onItemClick(View view, int position) {
        switch (view.getId()) {
            case R.id.iv_goods_reduce:
                allCount--;
                // 商品的数量是否显示
                if (allCount <= 0) {
                    allCount = 0;
                    mTvShoppingCartCount.setVisibility(View.GONE);
                } else {
                    mTvShoppingCartCount.setText(String.valueOf(allCount));
                    mTvShoppingCartCount.setVisibility(View.VISIBLE);
                }
                break;
            case R.id.iv_goods_add:
                allCount++;
                if (allCount > 0) {
                    mTvShoppingCartCount.setVisibility(View.VISIBLE);
                }
                mTvShoppingCartCount.setText(String.valueOf(allCount));
                addGoods2CartAnim((ImageView) view);
                break;
        }
    }


    /**
     * 贝塞尔曲线动画
     *
     * @param goodsImageView
     */
    public void addGoods2CartAnim(ImageView goodsImageView) {
        final ImageView goods = new ImageView(ProductActivity.this);
        goods.setImageResource(R.mipmap.icon_goods_add);
        int size = TextUtil.dp2px(ProductActivity.this, 24);
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(size, size);
        goods.setLayoutParams(lp);
        //mSnackLayout是整个页面布局id
        mSnackLayout.addView(goods);
        // 控制点的位置
        int[] recyclerLocation = new int[2];
        mSnackLayout.getLocationInWindow(recyclerLocation);
        // 加入点的位置起始点
        int[] startLocation = new int[2];
        goodsImageView.getLocationInWindow(startLocation);
        // 购物车的位置终点
        int[] endLocation = new int[2];
        //iv_shopping_cart是动画运行轨迹最终结束的地方  我这里用的是购物车的图标控件
        iv_shopping_cart.getLocationInWindow(endLocation);
        // TODO: 2018/5/21 0021 考虑到状态栏的问题,不然会往下偏移状态栏的高度
        int startX = startLocation[0] - recyclerLocation[0];
        int startY = startLocation[1] - recyclerLocation[1];
        // TODO: 2018/5/21 0021 和上面一样
        int endX = endLocation[0] - recyclerLocation[0];
        int endY = endLocation[1] - recyclerLocation[1];
        // 开始绘制贝塞尔曲线
        Path path = new Path();
        // 移动到起始点位置(即贝塞尔曲线的起点)
        path.moveTo(startX, startY);
        // 使用二阶贝塞尔曲线:注意第一个起始坐标越大,贝塞尔曲线的横向距离就会越大,一般按照下面的式子取即可
        path.quadTo((startX + endX) / 2, startY, endX, endY);
        // mPathMeasure用来计算贝塞尔曲线的曲线长度和贝塞尔曲线中间插值的坐标,如果是true,path会形成一个闭环
        final PathMeasure pathMeasure = new PathMeasure(path, false);
        // 属性动画实现(从0到贝塞尔曲线的长度之间进行插值计算,获取中间过程的距离值)
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, pathMeasure.getLength());
        // 计算距离
        int tempX = Math.abs(startX - endX);
        int tempY = Math.abs(startY - endY);
        // 根据距离计算时间
        int time = (int) (0.3 * Math.sqrt((tempX * tempX) + tempY * tempY));
        valueAnimator.setDuration(time);
        valueAnimator.start();
        valueAnimator.setInterpolator(new AccelerateInterpolator());
        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会自动填充上坐标,这个方法很重要。
                // mCurrentPosition此时就是中间距离点的坐标值
                pathMeasure.getPosTan(value, mCurrentPosition, null);
                // 移动的商品图片(动画图片)的坐标设置为该中间点的坐标
                goods.setTranslationX(mCurrentPosition[0]);
                goods.setTranslationY(mCurrentPosition[1]);
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // 移除图片
                mSnackLayout.removeView(goods);
                // 购物车数量增加
                mTvShoppingCartCount.setText(String.valueOf(allCount));
            }
        });
    }

}

特别注意:

(1)

     //mSnackLayout是整个页面布局id
     mSnackLayout.addView(goods);

(2)

   //iv_shopping_cart是动画运行轨迹最终结束的地方  我这里用的是购物车的图标控件
    iv_shopping_cart.getLocationInWindow(endLocation);

以上。

相关文章

  • 仿饿了么添加购物车动画

    原文网址 https://www.jianshu.com/p/b7bacc6805c2 根据以上方法结合自己的框架...

  • 仿饿了么添加购物车效果

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 最近有一个需求,要实现一个像饿了么添加购物车的...

  • Android停止Animation动画

    开发过程中遇到了仿饿了么购物车的icon的动画效果; 在动画执行过程中,如果点击跳转再返回,图片的动画效果会出现问...

  • 仿饿了么动画

    仿饿了么动画 最近项目Release完毕,闲暇之余给公司内部的小卖部app升下级(一个人撸完了design+cod...

  • Android 动画小练习

    张旭童大神的简书 仿饿了么加入购物车旋转控件 - 自带闪转腾挪动画 的按钮【注释张豪华版 Path酷炫动画】极速g...

  • 仿饿了么小球动画

    饿了么下单时,点击➕会有一个小球跳跃进入购物车的动画,公司有个商城页面要求模仿这个效果,在网上搜了下.都需要借助框...

  • 仿饿了么购物车下单效果

    仿饿了么购物车下单效果 前一段由于新项目需要,开发一个类似饿了么购物车下单效果,电商类、外卖类、点餐类项目都可以用...

  • [笔记] Vue 实现加入购物车效果 小球 抛物线

    参考 vue实现类似添加购物车抛物线效果(类似饿了么,京东到家)

  • 沪江学习Android端应用架构重构

    第154期:沪江学习Android端应用架构重构 Android开发 仿饿了么加入购物车旋转控件 - 自带闪转腾挪...

  • Android仿饿了么购物车效果

    先看下效果图: 1.首先列表布局采用Recycleview 2.购物车item布局 3.点击加号操作这里分二钟情况...

网友评论

    本文标题:仿饿了么添加购物车动画

    本文链接:https://www.haomeiwen.com/subject/xnundqtx.html