仿饿了么添加购物车动画

 

0 效果

效果图android

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布局效果以下图所示,主要是对加号和减号作动画。app

 

Item布局ide

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);

以上。动画

+qq群:853967238。获取以上高清技术思惟图,以及相关技术的免费视频学习资料