自定義動畫效果的Drawable

Android提供的的動畫系統有屬性動畫(Property Animation)、補間動畫(View Animation)和幀動畫(Drawable Animation)。如今比較經常使用的是屬性動畫,由於功能比較強大,正常咱們直接對視圖控件(View)進行屬性動畫的狀況比較多,下面來介紹下動畫效果Drawable的實現,相比View來說Drawable更簡單,使用起來也很方便。java

一. 自定義Drawable
android

自定義動畫Drawable只要繼承Drawable並實現如下4個方法,同時實現Animatable接口:git

public class CircleDrawable extends Drawable implements Animatable {
    @Override
    public void draw(Canvas canvas) {
        // 繪圖
    }

    @Override
    public void setAlpha(int alpha) {
        // 設置透明度
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        // 設置顏色過濾
    }

    @Override
    public int getOpacity() {
        // 設置顏色格式
        return PixelFormat.RGBA_8888;
    }

    @Override
    public void start() {
        // 啓動動畫
    }

    @Override
    public void stop() {
        // 中止動畫
    }

    @Override
    public boolean isRunning() {
        // 判斷動畫是否運行
        return false;
    }
}
在這幾個方法中咱們主要來處理Drawable的繪製,即 draw()方法,和自定義View同樣。咱們要實現動畫效果,也實現Animatable接口,它的3個方法都是和動畫相關,方法意圖也很明顯。下面來自定義一個圓圈逐漸擴散消失的效果:

/**
 * Created by long on 2016/7/2.
 * 圓圈Drawable
 */
public class CircleDrawable extends Drawable implements Animatable {

    private Paint mPaint;
    // 動畫控制
    private ValueAnimator mValueAnimator;
    // 擴散半徑
    private int mRadius;
    // 繪製的矩形框
    private RectF mRect = new RectF();
    // 動畫啓動延遲時間
    private int mStartDelay;

    // 自定義一個擴散半徑屬性
    Property<CircleDrawable, Integer> mRadiusProperty = new Property<CircleDrawable, Integer>(Integer.class, "radius") {
        @Override
        public void set(CircleDrawable object, Integer value) {
            object.setRadius(value);
        }

        @Override
        public Integer get(CircleDrawable object) {
            return object.getRadius();
        }
    };
    public int getRadius() {
        return mRadius;
    }
    public void setRadius(int radius) {
        mRadius = radius;
    }


    public CircleDrawable() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
    }


    @Override
    public void draw(Canvas canvas) {
        // 繪製圓圈
        canvas.drawCircle(mRect.centerX(), mRect.centerY(), mRadius, mPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.RGBA_8888;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        mRect.set(_clipSquare(bounds));
        if (isRunning()) {
            stop();
        }
        // 計算最大半徑
        int maxRadius = (int) ((mRect.right - mRect.left) / 2);
        // 控制擴散半徑的屬性變化
        PropertyValuesHolder radiusHolder = PropertyValuesHolder.ofInt(mRadiusProperty, 0, maxRadius);
        // 控制透明度的屬性變化
        PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofInt("alpha", 255, 0);
        mValueAnimator = ObjectAnimator.ofPropertyValuesHolder(this, radiusHolder, alphaHolder);
        mValueAnimator.setStartDelay(mStartDelay);
        mValueAnimator.setDuration(1200);
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 監聽屬性動畫並進行重繪
                invalidateSelf();
            }
        });
        // 設置動畫無限循環
        mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
        mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        start();
    }

    /**
     * 裁剪Rect爲正方形
     * @param rect
     * @return
     */
    private Rect _clipSquare(Rect rect) {
        int w = rect.width();
        int h = rect.height();
        int min = Math.min(w, h);
        int cx = rect.centerX();
        int cy = rect.centerY();
        int r = min / 2;
        return new Rect(
                cx - r,
                cy - r,
                cx + r,
                cy + r
        );
    }

    /************************************************************/

    @Override
    public void start() {
        mValueAnimator.start();
    }

    @Override
    public void stop() {
        mValueAnimator.end();
    }

    @Override
    public boolean isRunning() {
        return mValueAnimator != null && mValueAnimator.isRunning();
    }

    public void setAnimatorDelay(int startDelay) {
        mStartDelay = startDelay;
    }
}
整個流程仍是比較簡單,在構造函數裏對畫筆進行了初始化操做,複寫onBoundsChange(Rect bounds)接口來獲取圖形邊框參數,好比將Drawable設置給ImageView時,這裏就能獲取到ImageView的邊框大小。在這方法裏將邊框裁剪爲正方形,由於咱們要作圓圈Drawable嘛。而後剩下的就是屬性動畫的處理了,這裏自定義了一個擴散半徑屬性 mRadiusProperty,用來控制繪製圓圈的半徑,除了對半徑的控制外還有對透明度的控制。若是對自定義屬性Property和PropertyValuesHolder不清楚能夠看下這個: Androids屬性動畫PropertyValuesHolder的使用

來看下給ImageView設置咱們自定義的CircleDrawable的效果:github


二. 包含多個動畫Drawable
canvas

同理,實現包含多個動畫的自定義Drawable也須要繼承Drawable並實現Animatable接口,同時還要實現Drawable.Callback接口。先來看下Drawable.Callback的定義:數組

/*若是你想實現一個擴展子Drawable的動畫drawable,那麼你能夠經過setCallBack(android.graphics.drawable.Drawable.Callback)
 *來把你實現的該接口註冊到動畫drawable中。能夠實現對動畫的調度和執行 
 */   
public static interface Callback {  
    /** 
     * 當drawable重畫時觸發,這個點上drawable將被置爲不可用
     * @param 要求重畫的drawable 
     */  
    public void invalidateDrawable(Drawable who);  

    /** 
     * drawable能夠經過該方法來安排動畫的下一幀。能夠僅僅簡單的調用postAtTime(Runnable, Object, long) 
     * 來實現該方法。參數分別與方法的參數對應 
     * @param who The drawable being scheduled. 
     * @param what The action to execute. 
     * @param when The time (in milliseconds) to run 
     */  
    public void scheduleDrawable(Drawable who, Runnable what, long when);  

    /** 
     *能夠用於取消先前經過scheduleDrawable(Drawable who, Runnable what, long when)調度的某一幀。
     *能夠經過調用removeCallbacks(Runnable,Object)來實現 
     * @param who The drawable being unscheduled. 
     * @param what The action being unscheduled. 
     */  
    public void unscheduleDrawable(Drawable who, Runnable what);  
}
當咱們須要重繪Drawable時,會調用 invalidateSelf()接口,來看下它是怎麼操做的:

/**
 * Use the current {@link Callback} implementation to have this Drawable
 * redrawn.  Does nothing if there is no Callback attached to the
 * Drawable.
 *
 * @see Callback#invalidateDrawable
 * @see #getCallback()
 * @see #setCallback(android.graphics.drawable.Drawable.Callback)
 */
public void invalidateSelf() {
    final Callback callback = getCallback();
    if (callback != null) {
        callback.invalidateDrawable(this);
    }
}
須要若是給Drawable設置了 Drawable.Callback回調,就能夠監聽這個Drawable的重繪操做,並回調invalidateDrawable(Drawable who)方法。

好了,下面就能夠來開始自定義帶多個動畫的Drawable,直接複用上面寫的CircleDrawable,讓多個CircleDrawable動畫按順序執行:ide

/**
 * Created by long on 2016/7/2.
 * 複數Circle的Drawable,須要實現Drawable.Callback接口
 */
public class MultiCircleDrawable extends Drawable implements Animatable, Drawable.Callback {

    // 每一個Drawable動畫啓動的間隔
    private static final int EACH_CIRCLE_SPACE = 200;
    // CircleDrawable數組
    private CircleDrawable[] mCircleDrawables;


    public MultiCircleDrawable() {
        mCircleDrawables = new CircleDrawable[] {
                new CircleDrawable(),
                new CircleDrawable(),
                new CircleDrawable()
        };
        for (int i = 0; i < mCircleDrawables.length; i++) {
            // 設置動畫啓動延遲
            mCircleDrawables[i].setAnimatorDelay(EACH_CIRCLE_SPACE * i);
            // 設置回調監聽,當CircleDrawable發生重繪時就會調用 invalidateDrawable(Drawable who) 方法
            mCircleDrawables[i].setCallback(this);
        }
    }


    @Override
    public void draw(Canvas canvas) {
        for (CircleDrawable drawable : mCircleDrawables) {
            // 分層繪製每一個CircleDrawable
            int count = canvas.save();
            drawable.draw(canvas);
            canvas.restoreToCount(count);
        }
    }

    @Override
    public void setAlpha(int alpha) {
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
    }

    @Override
    public int getOpacity() {
        return PixelFormat.RGBA_8888;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        for (CircleDrawable drawable : mCircleDrawables) {
            drawable.onBoundsChange(bounds);
        }
    }
    /************************************************************/

    @Override
    public void start() {
        for (CircleDrawable drawable : mCircleDrawables) {
            drawable.start();
        }
    }

    @Override
    public void stop() {
        for (CircleDrawable drawable : mCircleDrawables) {
            drawable.stop();
        }
    }

    @Override
    public boolean isRunning() {
        for (CircleDrawable drawable : mCircleDrawables) {
            if (drawable.isRunning()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void invalidateDrawable(Drawable who) {
        // 須要重繪,子Drawable發生重繪會調用這個方法通知父Drawable,若是有設置Callback回調監聽的話
        invalidateSelf();
    }

    @Override
    public void scheduleDrawable(Drawable who, Runnable what, long when) {
    }

    @Override
    public void unscheduleDrawable(Drawable who, Runnable what) {
    }
}
能夠看到上面複用了三個CircleDrawable,並給它們設置了 動畫啓動延遲和 Drawable.Callback回調,並在回調方法invalidateDrawable(Drawable who)裏也對當前的MultiCircleDrawable進行重繪,即調用invalidateSelf()。這樣就完成了多個動畫Drawable的定義,來看下使用效果:

這只是簡單介紹自定義Drawable的使用,能夠本身定義更多好看的動畫效果,以上的源代碼:DrawableSample函數

自定義Drawable的運用能夠參考這個:實現360手機助手TabHost的波紋效果post

相關文章
相關標籤/搜索