天天看點

Android 屬性動畫-繪制貝塞爾曲線路徑

Android 屬性動畫-繪制貝塞爾曲線路徑

以前對屬性動畫的知識,隻是停留在值動畫和一般的移動、漸變、縮放,原來它還可以自定義,利用反射來回調自己的方法,真是設計的6

而且一直想了解路徑動畫是怎麼計算路徑的,看了别人的demo終于明白了,做下記錄和分析。

1、效果圖如下:

Android 屬性動畫-繪制貝塞爾曲線路徑

首先,來補充一下知識點,屬性動畫的設計原理

ObjectAnimator extends ValueAnimator屬性動畫內建值動畫

值動畫就是在指定時間内,把一個初始值根據估值器(變化規則)變成結束值

看下面的例子就會明白

ObjectAnimator    objectAnimator =  ObjectAnimator.ofInt(new MyObj(btn),"setX",0,300);//這裡一般我們會傳入view對象,
但是現在傳入一個我們自定義的對象
    objectAnimator.setDuration(5000);
 class MyObj{
    private View view;
    public MyObj(View view){
        this.view =view;
    }
// 可以看到我們這裡的方法,和熟悉“setX”很像,就是利用反射來的,如果我們以前傳入“alpha”,就是調用view的setAlpha方法設定屬性
// 對于參數int x,這裡就和我們傳入的0,300對應,當然,還可以自定義估值器,和變化的對象,這裡參數也就一一對應
    public void setSetX(int x){
        view.setTranslationX(x);
    }
}
           

補充完了之後,再來看重點,路徑動畫關鍵在于路徑是怎麼計算的,下面是看了别人的,改了一點點

1、自定義一個viewpath,包含viewpath規定的幾個類型,這幾個類型是用來描述點的,後面就會知道

public class ViewPath {
public static final int MOVE = 0;
public static final int LINE = 1;
public static final int QUAD = 2;
public static final int CURVE = 3;

private ArrayList<ViewPoint> mPoints;


public ViewPath() {
    mPoints = new ArrayList<>();
}

public void moveTo(float x, float y){
    mPoints.add(ViewPoint.moveTo(x,y,MOVE));
}

public void lineTo(float x,float y){
    mPoints.add(ViewPoint.lineTo(x,y,LINE));
}

public void curveTo(float x,float y,float x1,float y1,float x2,float y2){
    mPoints.add(ViewPoint.curveTo(x,y,x1,y1,x2,y2,CURVE));
}

public void quadTo(float x,float y,float x1,float y1){
    mPoints.add(ViewPoint.quadTo(x,y,x1,y1,QUAD));
}

public Collection<ViewPoint> getPoints(){
    return mPoints;
}


}

//這裡的viewpoint,如果是普通點,那麼他隻有x,y,如果是二階貝塞爾曲線的點,x,y就是控制點,x1,y1就是結束點,
如果是三階貝塞爾曲線的點,(x,y)(x1,y1)就是控制點,x2,y2就是結束點。
public class ViewPoint {
float x ,y;

float x1,y1;

float x2,y2;

int operation;

public ViewPoint() {

}
public ViewPoint(float x, float y) {
    this.x = x;
    this.y = y;
}

public static ViewPoint moveTo(float x, float y, int operation){
    return new ViewPoint(x,y,operation);
}

public static ViewPoint lineTo(float x, float y, int operation){
    return new ViewPoint(x,y,operation);
}
public static ViewPoint curveTo(float x, float y,float x1,float y1,float x2,float y2, int operation){
    return new ViewPoint(x,y,x1,y1,x2,y2,operation);
}

public static ViewPoint quadTo(float x, float y,float x1,float y1, int operation){
    return new ViewPoint(x,y,x1,y1,operation);
}



private ViewPoint(float x, float y, int operation) {
    this.x = x;
    this.y = y;
    this.operation = operation;
}

public ViewPoint(float x, float y, float x1, float y1, int operation) {
    this.x = x;
    this.y = y;
    this.x1 = x1;
    this.y1 = y1;
    this.operation = operation;
}

public ViewPoint(float x, float y, float x1, float y1, float x2, float y2, int operation) {
    this.x = x;
    this.y = y;
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
    this.operation = operation;
}
}
           

關鍵的來了,估值器(多個點,分别和開始點和結束點,根據結束點來計算規則)

public class ViewPathEvaluator implements TypeEvaluator<ViewPoint> {


public ViewPathEvaluator() {
}

@Override
public ViewPoint evaluate(float t, ViewPoint startValue, ViewPoint endValue) {

    float x  ,y;

    float startX,startY;
//判斷結束點的類型,根據後一個點類型,來計算開始點和結束點的變化


    if(endValue.operation == ViewPath.LINE){

        startX = (startValue.operation==ViewPath.QUAD)?startValue.x1:startValue.x;

        startX = (startValue.operation == ViewPath.CURVE)?startValue.x2:startX;

        startY = (startValue.operation==ViewPath.QUAD)?startValue.y1:startValue.y;

        startY = (startValue.operation == ViewPath.CURVE)?startValue.y2:startY;

        x = startX + t * (endValue.x - startX);
        y = startY+ t * (endValue.y - startY);



    }else if(endValue.operation == ViewPath.CURVE){
     //判斷開始點的類型,找到它真正的起始點,我就改了下這裡,原來别人的代碼的少判斷了一種情況

        startX = (startValue.operation==ViewPath.QUAD)?startValue.x1:startValue.x;
        startY = (startValue.operation==ViewPath.QUAD)?startValue.y1:startValue.y;

        startX = (startValue.operation==ViewPath.CURVE)?startValue.x2:startX;
        startY = (startValue.operation==ViewPath.CURVE)?startValue.y2:startY;

        float oneMinusT = 1 - t;
        //三階貝塞爾函數

        x = oneMinusT * oneMinusT * oneMinusT * startX +
                3 * oneMinusT * oneMinusT * t * endValue.x +
                3 * oneMinusT * t * t * endValue.x1+
                t * t * t * endValue.x2;

        y = oneMinusT * oneMinusT * oneMinusT * startY +
                3 * oneMinusT * oneMinusT * t * endValue.y +
                3 * oneMinusT * t * t * endValue.y1+
                t * t * t * endValue.y2;


    }else if(endValue.operation == ViewPath.MOVE){

        x = endValue.x;
        y = endValue.y;


    }else if(endValue.operation == ViewPath.QUAD){


        startX = (startValue.operation==ViewPath.CURVE)?startValue.x2:startValue.x;
        startY = (startValue.operation==ViewPath.CURVE)?startValue.y2:startValue.y;

        startX = (startValue.operation==ViewPath.QUAD)?startValue.x1:startX;
        startY = (startValue.operation==ViewPath.QUAD)?startValue.y1:startY;
    //二階貝塞爾函數
        float oneMinusT = 1 - t;
        x = oneMinusT * oneMinusT *  startX +
                2 * oneMinusT *  t * endValue.x +
                t * t * endValue.x1;

        y = oneMinusT * oneMinusT * startY +
                2  * oneMinusT * t * endValue.y +
                t * t * endValue.y1;


    }else {
        x = endValue.x;
        y = endValue.y;
    }


    return new ViewPoint(x,y);
}
}
           

前面的看懂之後,後面就簡單了,再來看下圖,把各個點找到效果就出來了

Android 屬性動畫-繪制貝塞爾曲線路徑
public class BezierPath extends View {
Paint paint1 = new Paint();
Paint paint2 = new Paint();
Paint paint3 = new Paint();
Paint paint4 = new Paint();
int radus = 300;
int time = 5000;
int width;
int height;
private ValueAnimator redAnim1;
private ValueAnimator redAnim2;
private ValueAnimator redAnim3;
private ValueAnimator redAnim4;
private ViewPoint cpoint =new ViewPoint();
private ViewPoint cpoint2=new ViewPoint();
private ViewPoint cpoint3=new ViewPoint();
private ViewPoint cpoint4=new ViewPoint();
private AnimatorSet animatorSet2;
private BezierPathListener bezierPathListener;


public BezierPath(Context context) {
    super(context);
    init();
}

public BezierPath(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public BezierPath(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

public void init() {
    paint1.setStyle(Paint.Style.FILL);
    paint1.setAntiAlias(true);
    paint1.setColor(Color.RED);
    paint2.setStyle(Paint.Style.FILL);
    paint2.setAntiAlias(true);
    paint2.setColor(Color.GREEN);

    paint3.setStyle(Paint.Style.FILL);
    paint3.setAntiAlias(true);
    paint3.setColor(Color.BLUE);
    paint4.setStyle(Paint.Style.FILL);
    paint4.setAntiAlias(true);
    paint4.setColor(Color.GRAY);
}

public void initPath() {
    //千萬不要覺得下面很複雜,就是找貝爾塞的控制點和結束點而已,很簡單
    //我們的ViewPath,其實可以繪制任何直線路徑和貝塞爾曲線路徑了,自己在調用lineTo傳入點等就行了

    ViewPath viewPath = new ViewPath();
    viewPath.moveTo(width / 2, height / 2);
    cpoint.x  = width/2;
    cpoint.y = height/2;
    viewPath.quadTo(width / 2 - radus , height / 2 - radus , width / 2, height / 2 - radus);
    viewPath.quadTo(width / 2 + radus , height / 2 - radus , width / 2, height / 2);
    redAnim1 = ValueAnimator.ofObject(new ViewPathEvaluator(), viewPath.getPoints().toArray());
    redAnim1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            cpoint = (ViewPoint) valueAnimator.getAnimatedValue();
            postInvalidate();
        }
    });
    redAnim1.setDuration(time);

    ViewPath viewPath2 = new ViewPath();
    viewPath2.moveTo(width / 2, height / 2);
    cpoint2.x  = width/2;
    cpoint2.y = height/2;
    viewPath2.quadTo(width / 2 + radus , height / 2 - radus , width / 2+radus, height / 2 );
    viewPath2.quadTo(width / 2 + radus , height / 2 + radus , width / 2, height / 2);
    redAnim2 = ValueAnimator.ofObject(new ViewPathEvaluator(), viewPath2.getPoints().toArray());
    redAnim2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            cpoint2 = (ViewPoint) valueAnimator.getAnimatedValue();
            postInvalidate();
        }
    });
    redAnim2.setDuration(time);

    ViewPath viewPath3 = new ViewPath();
    viewPath3.moveTo(width / 2, height / 2);
    cpoint3.x  = width/2;
    cpoint3.y = height/2;
    viewPath3.quadTo(width / 2+radus , height / 2 + radus , width / 2, height / 2+radus );
    viewPath3.quadTo(width / 2 - radus , height / 2 + radus , width / 2, height / 2);
    redAnim3 = ValueAnimator.ofObject(new ViewPathEvaluator(), viewPath3.getPoints().toArray());
    redAnim3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            cpoint3 = (ViewPoint) valueAnimator.getAnimatedValue();
            postInvalidate();
        }
    });
    redAnim3.setDuration(time);

    redAnim3.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animator) {
            isAnimationing = true;
        }

        @Override
        public void onAnimationEnd(Animator animator) {
            isAnimationing = false;
            if(null!=bezierPathListener){
                bezierPathListener.onAnimationEnd();
            }
        }

        @Override
        public void onAnimationCancel(Animator animator) {
            isAnimationing = false;
        }

        @Override
        public void onAnimationRepeat(Animator animator) {

        }
    });

    ViewPath viewPath4 = new ViewPath();
    viewPath4.moveTo(width / 2, height / 2);
    cpoint4.x  = width/2;
    cpoint4.y = height/2;
    viewPath4.quadTo(width / 2-radus , height / 2 + radus , width / 2-radus, height / 2 );
    viewPath4.quadTo(width / 2 - radus , height / 2 - radus , width / 2, height / 2);
    redAnim4 = ValueAnimator.ofObject(new ViewPathEvaluator(), viewPath4.getPoints().toArray());
    redAnim4.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            cpoint4 = (ViewPoint) valueAnimator.getAnimatedValue();
            postInvalidate();
        }
    });
    redAnim4.setDuration(time);

    animatorSet2 = new AnimatorSet();
    animatorSet2.playTogether(redAnim1,redAnim2,redAnim3,redAnim4);
    animatorSet2.setDuration(time);


}
public boolean isAnimationing = false;
boolean isInitPath = false;



@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    width = getMeasuredWidth();
    height = getMeasuredHeight();
    if (width > 0) {
        if (!isInitPath) {
            isInitPath = true;
            initPath();
        }
    }

}


@Override
public void draw(final Canvas canvas) {
    canvas.drawCircle(cpoint.x-20, cpoint.y-20, 25, paint1);
    canvas.drawCircle(cpoint2.x+20, cpoint2.y-20, 25, paint2);
    canvas.drawCircle(cpoint3.x+20, cpoint3.y+20, 25, paint3);
    canvas.drawCircle(cpoint4.x-20, cpoint4.y+20, 25, paint4);

}
public void startAnimation(){
    if (!isAnimationing) {
        animatorSet2.start();
    }
}
public void setListener(BezierPathListener bezierPathListener){
    this.bezierPathListener =bezierPathListener;
}
public  interface BezierPathListener{
    void onAnimationEnd();
}


}
           

源碼下載下傳

繼續閱讀