アニメーション効果をカスタマイズするDrawable


Androidが提供するアニメーションシステムには、プロパティアニメーション(Property Animation)、パッチアニメーション(View Animation)、フレームアニメーション(Drawable Animation)がある.現在よく使われているのは属性アニメーションですが、機能が強いため、通常はビューコントロール(View)を直接属性アニメーション化することが多いですが、アニメーション効果Drawableの実現について説明します.ViewよりDrawableの方が簡単で、使いやすいです.
一.カスタムDrawable
カスタムアニメーションDrawableは、Drawableを継承し、次の4つの方法を実装し、Animatableインタフェースを実装します.
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が不明な場合は、AndroidアトリビュートアニメーションPropertyValuesHolderの使用を参照してください.
ImageViewにカスタマイズしたCircleDrawableの効果を設定します.
二.複数のアニメーションを含むDrawable
同様に、複数のアニメーションを含むカスタムDrawableを実現するにはDrawableを継承しAnimatableインタフェースを実現するとともにDrawableを実現する必要がある.Callbackインタフェース.まず見てみましょう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アニメーションを順番に実行します.
/**
 * 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) {
    }
}
には、3つのCircleDrawableが多重化され、設定されていることがわかります.
アニメーションの開始遅延と
Drawable.Callbackコールバックは、コールバックメソッドinvalidateDrawable(Drawable who)でも現在のMultiCircleDrawableを再描画し、invalidateSelf()を呼び出します.これで複数のアニメーションDrawableの定義が完了し、使用効果を見てみましょう.
これは、カスタムDrawableの使用を簡単に紹介するだけで、より美しいアニメーション効果を自分で定義することができます.以上のソースコード:DrawableSample.
カスタムDrawableの運用はこれを参考にすることができます:360携帯電話アシスタントTabHostの波紋効果を実現します