1. 程式人生 > >購物車特效-貝塞爾曲線動畫(點選新增按鈕,產生拋物線動畫效果)

購物車特效-貝塞爾曲線動畫(點選新增按鈕,產生拋物線動畫效果)

demo效果:


購物車特效原理:

1.從新增按鈕獲取開始座標

2.從購物車圖示獲取結束座標

3.打氣一個檢視,新增屬性動畫ObjectAnimator(縮小),ValueAnimator(路線)

4.動畫開始時新增該檢視,動畫結束刪除該檢視

5.運動路徑使用TypeEvaluator與貝塞爾函式計算


activity佈局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!--貝塞爾曲線動畫自定義控制元件-->
    <custom.BezierAnim
        android:id="@+id/bezier_anim"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!--新增按鈕-->
    <Button
        android:id="@+id/bt_good"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_margin="10dp"
        android:text="+" />
    <!--購物車-->
    <ImageView
        android:id="@+id/iv_cart"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:src="@drawable/cart91" />
</RelativeLayout>


移動控制元件佈局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:orientation="vertical">

    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/coin91" />

</RelativeLayout>

貝塞爾曲線動畫的自定義控制元件編寫:

public class BezierAnim extends FrameLayout {
    public BezierAnim(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public BezierAnim(Context context) {
        this(context, null, 0);
    }

    public BezierAnim(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    //PointF等價於float[]陣列,裡面存放的x和y的值 (point相當於int[])
    private PointF mLocation = new PointF();//這樣創建出來,裡面還沒有值

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        //獲取當前父佈局在介面的螢幕座標(也就是父佈局左上角座標)
        int[] layoutLoc = new int[2];
        getLocationInWindow(layoutLoc);
        mLocation.set(layoutLoc[0], layoutLoc[1]);//將父佈局左上角的值賦值給mLocation
    }

    /**
     * 開始貝塞爾動畫
     *
     * @param startView    動畫從哪個view開始(+號)
     * @param endView      動畫在哪個view結束(購物車)
     * @param layoutIdMove 動畫作用的移動控制元件(錢袋子的佈局)
     */
    public void startCartAnim(View startView, View endView, int layoutIdMove) {
        //1,開始位置
        int[] startLoc = new int[2];
        startView.getLocationInWindow(startLoc);//獲取當前view在螢幕上的座標
        PointF startF = new PointF(startLoc[0] - mLocation.x, startLoc[1] - mLocation.y);//得到當前view相對於父佈局左上角位置的座標
        // 2,結束位置
        int[] endLoc = new int[2];
        endView.getLocationInWindow(endLoc);
        final PointF endF = new PointF(endLoc[0] - mLocation.x, endLoc[1] - mLocation.y);
        //3.移動控制元件。inflate()引數:作用佈局,參考佈局,false
        final View moveView = LayoutInflater.from(getContext()).inflate(layoutIdMove, this, false);
        //開始動畫  使用屬性動畫合集
        AnimatorSet set = new AnimatorSet();
        //縮小動畫
        ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(moveView, "scaleX", 1.0f, 0.1f);
        ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(moveView, "scaleY", 1.0f, 0.1f);
        //路徑動畫(baisaier曲線路徑,開始座標,結束座標)
        ValueAnimator pathAnim = ObjectAnimator.ofObject(beisaier, startF, endF);
        pathAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //更新座標
                PointF newPointF = (PointF) animation.getAnimatedValue();
                moveView.setX(newPointF.x);
                moveView.setY(newPointF.y);
            }
        });
        //將這些動畫放入集合中
        set.playTogether(scaleXAnim, scaleYAnim, pathAnim);
        Animator.AnimatorListener listener = new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                BezierAnim.this.addView(moveView);//加入動畫作用的控制元件
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                BezierAnim.this.removeView(moveView);//移除動畫作用的控制元件
            }

            @Override
            public void onAnimationCancel(Animator animation) {}

            @Override
            public void onAnimationRepeat(Animator animation) {}
        };
        set.addListener(listener);//動畫播放監聽器
        set.setDuration(1000);  //運動時間
        set.start();            
    }

    //路徑計算器
    private TypeEvaluator<PointF> beisaier = new TypeEvaluator<PointF>() {
        @Override
        public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
            //返回變化的軌跡座標
            PointF newF = new PointF((startValue.x + endValue.x) / 2, 0);//控制點
            return BezierCurve.bezier(fraction, startValue, newF, endValue);
        }
    };
}


路徑計算器:
public class BezierCurve {
    /**
     * 二次貝塞爾曲線插值
     * t:值範圍from = 0, to = 1
     */
    public static PointF bezier(float t, PointF point0, PointF point1, PointF point2) {
        float oneMinusT = 1.0f - t;
        PointF point = new PointF();
        point.x = oneMinusT * oneMinusT * point0.x
                + 2 * t * oneMinusT * point1.x
                + t * t * point2.x;
        point.y = oneMinusT * oneMinusT * point0.y
                + 2 * t * oneMinusT * point1.y
                + t * t * point2.y;
        return point;
    }

    /**
     * 三次貝塞爾曲線插值
     * t:值範圍from = 0, to = 1
     */
    public static PointF bezier(float t, PointF point0, PointF point1, PointF point2, PointF point3) {
        float oneMinusT = 1.0f - t;
        PointF point = new PointF();
        point.x = oneMinusT * oneMinusT * oneMinusT * (point0.x)
                + 3 * oneMinusT * oneMinusT * t * (point1.x)
                + 3 * oneMinusT * t * t * (point2.x)
                + t * t * t * (point3.x);

        point.y = oneMinusT * oneMinusT * oneMinusT * (point0.y)
                + 3 * oneMinusT * oneMinusT * t * (point1.y)
                + 3 * oneMinusT * t * t * (point2.y)
                + t * t * t * (point3.y);
        return point;
    }
}

activity開始動畫:
public class CartBazierActivity extends Activity {
    @InjectView(R.id.bt_good)
    Button btGood;
    @InjectView(R.id.iv_cart)
    ImageView ivCart;
    @InjectView(R.id.bezier_anim)
    BezierAnim bezierAnim;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cart_bezier);
        ButterKnife.inject(this);
    }

    @OnClick(R.id.bt_good)
    public void bezierMove() {
        Toast.makeText(this, "添加了一件商品", Toast.LENGTH_SHORT).show();
        bezierAnim.startCartAnim(btGood, ivCart, R.layout.moveview);
    }
}

若是startView和endView不在一個介面,可以用EventBus傳值