Android 屬性動畫:實現購物車添加商品動畫

實現需求是:

在商品列表頁面,從列表Item 添加商品的時候,需要一個動畫,彷彿是是往購物車裏添加商品。

實現思路:

  1. 獲取起始點與終點的座標,利用PathMeasure 繪製貝塞爾曲線;
  2. 爲點擊的Item 商品View 設置屬性動畫;
  3. 監聽屬性動畫的update,改變View 的座標;

實現效果如下:

這裏寫圖片描述

實現中會用到 PathMeasure 類:
我們主要使用它兩個方法:

1、獲取長度:

/** //獲取弧線的總長度(周長)
     * Return the total length of the current contour, or 0 if no path is
     * associated with this measure object.
     */
    public float getLength() {
        return native_getLength(native_instance);//系統調用native 方法;
    }

2、獲取座標:

/**
     * Pins distance to 0 <= distance <= getLength(), and then computes the
     * corresponding position and tangent. Returns false if there is no path,
     * or a zero-length path was specified, in which case position and tangent
     * are unchanged.
     *
     * @param distance The distance along the current contour to sample
     * @param pos If not null, eturns the sampled position (x==[0], y==[1])
     * @param tan If not null, returns the sampled tangent (x==[0], y==[1])
     * @return false if there was no path associated with this measure object
    */
    public boolean getPosTan(float distance, float pos[], float tan[]) {
        if (pos != null && pos.length < 2 ||
            tan != null && tan.length < 2) {
            throw new ArrayIndexOutOfBoundsException();
        }
        return native_getPosTan(native_instance, distance, pos, tan);
    }

方法 getPosTan(float distance, float pos[],float tan[]) - path 爲 null ,返回 false
distance 爲一個 0 - getLength() 之間的值,根據這個值 PathMeasure 會計算出當前點的座標封裝到 pos 中。上面這句話我們可以這麼來理解,不管實際 Path 多麼的複雜,PathMeasure 都相當於做了一個事情,就是把 Path “拉直”,然後給了我們一個接口(getLength)告訴我們path的總長度,然後我們想要知道具體某一點的座標,只需要用相對的distance去取即可,這樣就省去了自己用函數模擬path,然後計算獲取點座標的過程。

代碼如下:

public class GoodsListActivity extends AppCompatActivity {

    private RelativeLayout mRootRl;
    private RecyclerView mGoodsRecyclerView;
    private ImageView mCarImageView;
    private TextView mCountTv;

    private List<Bitmap> mBitmapList = new ArrayList<>();
    private PathMeasure mPathMeasure;
    private float[] mCurrentPosition = new float[2];
    private int mCount = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_goods_list);
        initView();
        initData();
        GoodsAdapter goodsAdapter = new GoodsAdapter(mBitmapList);
        mGoodsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mGoodsRecyclerView.setAdapter(goodsAdapter);
    }

    private void initView(){
        mGoodsRecyclerView = (RecyclerView)findViewById(R.id.recyclerView);
        mCarImageView = (ImageView)findViewById(R.id.imageview_shop_car);
        mCountTv = (TextView)findViewById(R.id.tv_count);
        mRootRl = (RelativeLayout)findViewById(R.id.rl_root);
    }

    private void initData(){
        mBitmapList.add(BitmapFactory.decodeResource(getResources(), R.drawable.car));
        mBitmapList.add(BitmapFactory.decodeResource(getResources(), R.drawable.car));
        mBitmapList.add(BitmapFactory.decodeResource(getResources(), R.drawable.car));
    }

    class GoodsAdapter extends RecyclerView.Adapter<GoodsViewHolder>{

        private List<Bitmap> mData;

        public GoodsAdapter(List<Bitmap> data) {
            mData = data;
        }

        @Override
        public GoodsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View itemView = LayoutInflater.from(GoodsListActivity.this)
                    .inflate(R.layout.rv_goods_item, parent, false);
            return new GoodsViewHolder(itemView);
        }

        @Override
        public void onBindViewHolder(final GoodsViewHolder holder, int position) {
            holder.ivGood.setImageBitmap(mData.get(position));
            holder.tvBuy.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    addGoodToCar(holder.ivGood);
                }
            });
        }

        @Override
        public int getItemCount() {
            return mData != null ? mData.size() : 0;
        }
    }

    private void addGoodToCar(ImageView imageView){
        final ImageView view = new ImageView(GoodsListActivity.this);
        view.setImageDrawable(imageView.getDrawable());
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(100, 100);
        mRootRl.addView(view, layoutParams);

        //二、計算動畫開始/結束點的座標的準備工作
        //得到父佈局的起始點座標(用於輔助計算動畫開始/結束時的點的座標)
        int[] parentLoc = new int[2];
        mRootRl.getLocationInWindow(parentLoc);

        //得到商品圖片的座標(用於計算動畫開始的座標)
        int startLoc[] = new int[2];
        imageView.getLocationInWindow(startLoc);

        //得到購物車圖片的座標(用於計算動畫結束後的座標)
        int endLoc[] = new int[2];
        mCarImageView.getLocationInWindow(endLoc);

        float startX = startLoc[0] - parentLoc[0] + imageView.getWidth()/2;
        float startY = startLoc[1] - parentLoc[1] + imageView.getHeight()/2;

        //商品掉落後的終點座標:購物車起始點-父佈局起始點+購物車圖片的1/5
        float toX = endLoc[0] - parentLoc[0] + mCarImageView.getWidth() / 5;
        float toY = endLoc[1] - parentLoc[1];

        //開始繪製貝塞爾曲線
        Path path = new Path();
        path.moveTo(startX, startY);
        //使用二次薩貝爾曲線:注意第一個起始座標越大,貝塞爾曲線的橫向距離就會越大,一般按照下面的式子取即可
        path.quadTo((startX + toX) / 2, startY, toX, toY);
        mPathMeasure = new PathMeasure(path, false);

        //屬性動畫
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
        valueAnimator.setDuration(1000);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float)animation.getAnimatedValue();
                mPathMeasure.getPosTan(value, mCurrentPosition, null);
                view.setTranslationX(mCurrentPosition[0]);
                view.setTranslationY(mCurrentPosition[1]);
            }
        });
        valueAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                // 購物車的數量加1
                mCount++;
                mCountTv.setText(String.valueOf(mCount));
                // 把移動的圖片imageview從父佈局裏移除
                mRootRl.removeView(view);

                //shopImg 開始一個放大動畫
                Animation scaleAnim = AnimationUtils.loadAnimation(GoodsListActivity.this, R.anim.shop_car_scale);
                mCarImageView.startAnimation(scaleAnim);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        valueAnimator.start();
    }

    class GoodsViewHolder extends RecyclerView.ViewHolder{

        private ImageView ivGood;
        private TextView tvBuy;

        public GoodsViewHolder(View itemView) {
            super(itemView);
            ivGood = (ImageView)itemView.findViewById(R.id.iv_goods);
            tvBuy = (TextView) itemView.findViewById(R.id.tv_buy);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章