1. 程式人生 > >Android動畫篇(四):最終效果篇CircleProgressSuperBar

Android動畫篇(四):最終效果篇CircleProgressSuperBar

前言

今天終於有時間把最後的成果分享給大家了,為了提高一下部落格的逼格,我也找了一個專門做原型、導圖的線上網站:processon(www.processon.com),這個工具真的很棒,也很方便,這裡給他點個贊。

CircleProgressSuperBar為了完成最終的效果,我也是踩了一些坑,今天把我總結的最清晰的思路分享給大家,首先我們回顧一下我們的效果圖:

這裡寫圖片描述

正文

首先我們來分析一下,CircleProgressSuperBar總共分為幾種狀態:

這裡寫圖片描述

這四種狀態,我們不關心是怎麼切換的,我們只關心動畫是怎麼過渡的。

首先我們知道準備一些類:

1、CircleProgressSuperBar,這個是主類,也是最終要完成的view。
2、四個狀態的Drawable,我們分別命名為:NormalDrawable、LoadingDrawable、CompleteDrawable和ErrorDrawable。

最終為了方便擴充套件和解耦,我最終實現的架構圖是這樣的:

這裡寫圖片描述

我的主要目的:

1、在View和Drawable之間建立工廠類,即能降低類之間的耦合,也可以方便擴充套件更多狀態的Drawable。

2、各種狀態Drawble的基類BaseStatusDrawable,封裝公共的屬性和方法,讓View直接使用BaseStatusDrawable型別,而不去具體關心具體的Drawable的實現。

3、BaseStatusDrawable內部帶有樣式的資訊,防止和內部的畫筆有關的顏色弄混。

那我們就從最基礎的部分,首先新建CircleProgressSuperInfo:

public class CircleProgressSuperInfo {

    /**
     * 寬, 在設定動畫的時候需要知道寬
     */
    private int mWidth;

    /**
     * 高,在設定動畫的時候需要知道高
     */
    private int mHeight;

    /**
     * 圓角
     */
    private int mRadius;

    /**
     * 背景顏色
     */
    private int mBgColor;

    /**
     * 邊框顏色
     */
    private
int mBorderColor; /** * 邊框的寬度 */ private int mBorderWidth; /** * 最大的間距 * */ private float mPadding; public CircleProgressSuperInfo(int bgColor, int borderColor, int borderWidth) { this.mBgColor = bgColor; this.mBorderColor = borderColor; this.mBorderWidth = borderWidth; } ... // 此處省略setter和getter方法 }

然後就是需要BaseStatusDrawable,我們直接把之前寫好的形狀變化的ChangeShapeAndColorButton進行改造,變成我們需要的BaseStatusDrawable:

/**
 * Created by li.zhipeng on 2017/7/12.
 * <p>
 * 所有的狀態圖片需要實現此介面
 */
public abstract class BaseStatusDrawable extends Drawable {

    protected CircleProgressSuperInfo mInfo;

    /**
     * 寬, 在設定動畫的時候需要知道寬
     */
    protected int mWidth;

    /**
     * 高,在設定動畫的時候需要知道高
     */
    protected int mHeight;

    /**
     * 圓角
     */
    protected float mRadius;

    /**
     * 文字
     */
    protected String mText;

    /**
     * 文字顏色
     */
    protected int mTextColor = Color.parseColor("#ffffff");

    /**
     * 文字大小
     */
    protected int mTextSize;

    /**
     * 畫筆
     */
    protected Paint mPaint;

    /**
     * 形狀
     */
    protected RectF mRectF;


    /**
     * 背景顏色
     */
    protected int mBgColor;

    /**
     * 邊框顏色
     */
    protected int mBorderColor;

    /**
     * 邊框的寬度
     */
    protected int mBorderWidth;

    /**
     * 偏移值,也就是大小要發生的變化值
     */
    protected float mPadding;

    /**
     * 最小大小
     */
    protected float mMinSize = -1;

    /**
     * 正在動畫在中
     */
    protected boolean isAnim;

    public BaseStatusDrawable(CircleProgressSuperInfo info) {
        this.mInfo = info;
        // 初始化畫筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setDither(true);
        mRectF = new RectF();
    }

    // 此處省略各種setter和getter方法
    ...

    public CircleProgressSuperInfo getInfo(){return this.mInfo;};

    /**
     * 回到後臺,呼叫此方法
     */
    public abstract void release();

    /**
     * 繪製
     */
    @Override
    public void draw(@NonNull Canvas canvas) {
        // 這是繪製過渡動畫
        if (isAnim()) {
            drawTransition(canvas);
        }
        // 繪製正常狀態,例如loading就要使用繪製旋轉的圓圈
        else {
            drawSelf(canvas);
        }
    }

    /**
     * 繪製過度動畫
     */
    protected void drawTransition(Canvas canvas) {
        // 先畫出背景,背景是居中的
        // 判斷寬高
        int width = getWidth();
        int height = getHeight();

        // 計算左右的間距值,並且判斷不能小於minSize
        float paddingLR = width - mPadding * 2 < mMinSize ? (width - mMinSize) / 2 : mPadding;
        float paddingTB = height - mPadding * 2 < mMinSize ? (height - mMinSize) / 2 : mPadding;

        // 繪製描邊
        mRectF.set(paddingLR + mBorderWidth / 2, paddingTB + mBorderWidth / 2, getWidth() - paddingLR - mBorderWidth / 2,
                getHeight() - paddingTB - mBorderWidth / 2);

        // 開始畫後面的背景
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mBgColor);
        canvas.drawRoundRect(mRectF, mRadius, mRadius, mPaint);


        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mBorderColor);
        mPaint.setStrokeWidth(mBorderWidth);
        canvas.drawRoundRect(mRectF, mRadius, mRadius, mPaint);

        // 居中繪製文字
        if (!TextUtils.isEmpty(mText)) {
            float textDescent = mPaint.getFontMetrics().descent;
            float textAscent = mPaint.getFontMetrics().ascent;
            float delta = Math.abs(textAscent) - textDescent;
            mPaint.setColor(mTextColor);
            mPaint.setTextSize(mTextSize);
            float textWidth = mPaint.measureText(mText);
            canvas.drawText(mText, (width - textWidth) / 2, height / 2 + delta / 2, mPaint);
        }
    }


    /**
     * 繪製正常狀態
     */
    public abstract void drawSelf(Canvas canvas);


    @Override
    public void setAlpha(@IntRange(from = 0, to = 255) int i) {

    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
    }

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

這裡要強調幾點:

1、繼承Drawable的原因:主要是為了重繪,例如之後的載入狀態,是需要不斷重繪的才會有轉圈的動畫,但是已經和View分離了,就無法藉助View.invaliate(),所以這裡繼承了Drawable。

2、drawSelf()是繪製非過渡動畫的狀態,這裡主要是給LoadingDrawable用的。

3、getOpacity()方法的作用:返回圖片的質量型別。

為了理解getOpacity的作用,我們就先看一下他的原始碼和註釋:

// 擷取的註釋,getOpacity值能返回一下幾個值
PixelFormat.UNKNOWN
PixelFormat.TRANSLUCENT
PixelFormat.TRANSPARENT
PixelFormat.OPAQUE

// 系統自動適配
public static final int UNKNOWN     = 0;

// 簡單的說就是支援半透明
public static final int TRANSLUCENT = -3;

// 支援全透明
public static final int TRANSPARENT = -2;

// 不支援透明
public static final int OPAQUE      = -1;

屬性的命名規則就是見字如見人,字面意思就是這樣。其他兩個方法,這裡沒有用到就不說明了,有興趣的可以自己去研究研究。

這個時候去就可以建立四個狀態的Drawable了,普通狀態、完成狀態還有錯誤狀態都是一樣的,所以就只貼一個類的程式碼了:

/**
 * Created by li.zhipeng on 2017/7/12.
 * <p>
 * 正常狀態下的圖片
 */

public class NormalDrawable extends BaseStatusDrawable {

    public NormalDrawable(CircleProgressSuperInfo info) {
        super(info);
        // 普通狀態的顏色要設定,其他的不用設定
        setBgColor(info.getBgColor());
        setBorderColor(info.getBorderColor());
    }

    @Override
    public void drawSelf(Canvas canvas) {
        drawTransition(canvas);
    }

    @Override
    public void release() {

    }

}

重點是LoadingDrawable,其實也很簡單,因為繼承的關係,現在只需要關心繪製載入狀態就足夠了,這個時候把我們第一篇CircleProgressBar進行改造:

/**
 * Created by li.zhipeng on 2017/7/12.
 * <p>
 * 載入狀態或是進度條的drawable
 */

public class LoadingDrawable extends BaseStatusDrawable {

    /**
     * 圓周的角度
     */
    private static final Float CIRCULAR = 360f;

    /**
     * 進度
     */
    private float mProgress = 50;

    /**
     * 最大進度
     */
    private int mMaxProgress = 100;

    /**
     * 邊框顏色,也就是進度的顏色
     */
    private int mProgressColor = Color.parseColor("#ff00ff");

    /**
     * 繪製的不全進度的顏色
     */
    private int mDrawBorderColor;

    /**
     * 要繪製的進度條的顏色
     */
    private int mDrawProgressColor;

    /**
     * 是否開啟過度模式,也就是我們平時看到的類似追趕的效果
     */
    private boolean mIsIntermediateMode = true;

    /**
     * 最小弧度,進度條過度模式最小的弧度
     */
    private int mMinProgress = 5;

    /**
     * 過度動畫的時間
     */
    private static final int DURATION = 1000;

    /**
     * 過度動畫
     */
    private ValueAnimator valueAnimator;

    /**
     * 開始角度,在過度動畫中使用
     */
    private float mStartAngle = -90f;

    public LoadingDrawable(CircleProgressSuperInfo info) {
        super(info);
    }

    /**
     * 設定進度
     */
    public void setProgress(float progress) {
        this.mProgress = progress;
    }

    /**
     * 獲取進度條的顏色
     */
    public int getProgressColor() {
        return this.mProgressColor;
    }

    /**
     * 設定進度條的顏色
     */
    public void setProgressColor(int color) {
        this.mProgressColor = color;
    }

    /**
     * 設定進度條的顏色
     */
    public void setDrawProgressColor(int color) {
        this.mDrawProgressColor = color;
    }

    public int getDrawProgressColor() {
        return mDrawProgressColor;
    }

    public int getDrawBorderColor() {
        return mDrawBorderColor;
    }

    public void setDrawBorderColor(int mDrawBorderColor) {
        this.mDrawBorderColor = mDrawBorderColor;
    }

    /**
     * 是否是過度模式
     */
    public boolean isIntermediateMode() {
        return mIsIntermediateMode;
    }

    /**
     * 設定繪製區域
     */
    @Override
    public void setRadius(float radius) {
        super.setRadius(radius);
        // 計算要繪製的區域
        mRectF.set(mWidth / 2 - mRadius + mBorderWidth / 2, mHeight / 2 - mRadius + mBorderWidth / 2,
                mWidth / 2 + mRadius - mBorderWidth / 2, mHeight / 2 + mRadius - mBorderWidth / 2);
    }

    /**
     * 設定loading模式
     */
    public void setIntermediateMode(boolean intermediateMode) {
        if (mIsIntermediateMode != intermediateMode) {
            this.mIsIntermediateMode = intermediateMode;
            // 取消動畫
            if (!mIsIntermediateMode) {
                valueAnimator.cancel();
            } else {
                //這裡要開啟動畫
                startIntermediateAnim();
            }
        }
    }

    @Override
    public void drawSelf(Canvas canvas) {
        // 是否要顯示loading狀態
        if (mIsIntermediateMode) {
            startIntermediateAnim();
            drawIntermediateProgress(canvas);
        }
        // 繪製進度條
        else {
            drawProgress(canvas);
        }
    }

    /**
     * 繪製過度進度條
     */
    private void drawIntermediateProgress(Canvas canvas) {
        // 首先畫出背景圓
        mPaint.setColor(mBgColor);
        mPaint.setStyle(Paint.Style.FILL);
        // 這裡減去了邊框的寬度
        canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius - mBorderWidth, mPaint);
        // 畫出進度條
        mPaint.setColor(mDrawProgressColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mBorderWidth);
        // 計算圓弧劃過的角度
        float angle = CIRCULAR / mMaxProgress * mProgress;
        // 這裡要畫圓弧
        canvas.drawArc(mRectF, mStartAngle, angle, false, mPaint);

        // 畫出另一部分的進度條
        mPaint.setColor(mDrawBorderColor);
        mPaint.setStrokeWidth(mBorderWidth);
        // 這裡要畫圓弧
        canvas.drawArc(mRectF, mStartAngle + angle, CIRCULAR - angle, false, mPaint);
    }

    /**
     * 繪製進度條
     */
    private void drawProgress(Canvas canvas) {
        // 開始畫進度條
        // 首先畫出背景圓
        mPaint.setColor(mBgColor);
        mPaint.setStyle(Paint.Style.FILL);
        // 這裡減去了邊框的寬度
        canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius - mBorderWidth, mPaint);
        // 畫出進度條
        mPaint.setColor(mDrawProgressColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mBorderWidth);

        // 計算圓弧劃過的角度
        float angle = CIRCULAR / mMaxProgress * mProgress;
        // 這裡要畫圓弧
        canvas.drawArc(mRectF, -90, angle, false, mPaint);
        // 畫出另一部分的進度條
        mPaint.setColor(mBorderColor);
        mPaint.setStrokeWidth(mBorderWidth);
        // 這裡要畫圓弧
        canvas.drawArc(mRectF, -90 + angle, CIRCULAR - angle, false, mPaint);
    }

    /**
     * 開始過度動畫
     */
    private synchronized void startIntermediateAnim() {
        if (valueAnimator != null && valueAnimator.isStarted()) {
            return;
        }

        if (valueAnimator == null) {
            valueAnimator = new ValueAnimator().ofFloat(mMinProgress, mMaxProgress - mMinProgress);
            valueAnimator.setDuration(DURATION);
            valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    float value = (float) valueAnimator.getAnimatedValue();
                    setProgress(value);
                    mStartAngle += 2;
                    invalidateSelf();
                }

            });
            valueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {

                }

                @Override
                public void onAnimationEnd(Animator animator) {

                }

                @Override
                public void onAnimationCancel(Animator animator) {
                    // 充值旋轉的角度
                    mStartAngle = -90;
                }

                @Override
                public void onAnimationRepeat(Animator animator) {
                    // 互換顏色和位置
                    mStartAngle = mStartAngle - CIRCULAR / mMaxProgress * mMinProgress;
                    int color = getDrawProgressColor();
                    setDrawProgressColor(getDrawBorderColor());
                    setDrawBorderColor(color);
                }
            });
        }
        // 開始動畫的時候,要重新設定顏色,否則顏色可能會錯亂,因為在動畫的過程已經互換
        setDrawProgressColor(mProgressColor);
        setDrawBorderColor(mBorderColor);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.start();
    }

    /**
     * 停止動畫
     */
    private void stopAnim() {
        if (valueAnimator != null && valueAnimator.isRunning()) {
            valueAnimator.cancel();
        }
    }

    @Override
    public void release() {
        stopAnim();
    }

}

幾乎是沒有什麼變化,增加了開始動畫和結束動畫方法,這樣其他的狀態時,可以節省系統資源。

然後是工廠類:

/**
 * Created by li.zhipeng on 2017/7/13.
 * <p>
 * 生產不同狀態的Drawable的生產類
 */

public class StatusDrawableFactory {

    private static StatusDrawableFactory mInstance;

    public synchronized static StatusDrawableFactory getInstance() {
        if (mInstance == null) {
            mInstance = new StatusDrawableFactory();
        }
        return mInstance;
    }

    /**
     * 返回指定狀態的drawable
     * */
    public BaseStatusDrawable getDrawable(int status, CircleProgressSuperInfo info) {
        BaseStatusDrawable drawable = null;
        switch (status) {
            case Status.NORMAL:
                drawable = new NormalDrawable(info);
                break;
            case Status.LOADING:
                drawable = new LoadingDrawable(info);
                break;
            case Status.COMPLETE:
                drawable = new CompleteDrawable(info);
                break;
            case Status.ERROR:
                drawable = new ErrorDrawable(info);
                break;

        }
        return drawable;
    }

}

非常簡單的單例模式,返回指定的BaseStatusDrawable型別。

最後就是CircleProgressSuperBar:

/**
 * Created by li.zhipeng on 2017/7/12.
 * <p>
 * 具有多狀態的CircleProgressBar,整合前兩個控制元件的效果
 */

public class CircleProgressSuperBar extends View {

    /**
     * 儲存四張狀態的Drawable
     */
    private BaseStatusDrawable[] drawables = new BaseStatusDrawable[4];

    /**
     * 測試就只要一個xml的構造方法就足夠了
     */
    public CircleProgressSuperBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        // 初始化資訊類
        drawables[Status.NORMAL] = StatusDrawableFactory.getInstance().getDrawable(Status.NORMAL,
                new CircleProgressSuperInfo(Color.parseColor("#3399ff"), Color.parseColor("#3399ff"), 10));
        drawables[Status.LOADING] = StatusDrawableFactory.getInstance().getDrawable(Status.LOADING,
                new CircleProgressSuperInfo(Color.parseColor("#ff0000"), Color.parseColor("#000000"), 10));
        drawables[Status.COMPLETE] = StatusDrawableFactory.getInstance().getDrawable(Status.COMPLETE,
                new CircleProgressSuperInfo(Color.parseColor("#ffcc00"), Color.parseColor("#ffcc00"), 10));
        drawables[Status.ERROR] = StatusDrawableFactory.getInstance().getDrawable(Status.ERROR,
                new CircleProgressSuperInfo(Color.parseColor("#ff3300"), Color.parseColor("#ff3300"), 10));

        drawables[Status.NORMAL].setText("Normal");
        drawables[Status.ERROR].setText("Error");
        drawables[Status.COMPLETE].setText("Complete");

        drawables[Status.NORMAL].setTextSize(42);
        drawables[Status.ERROR].setTextSize(42);
        drawables[Status.COMPLETE].setTextSize(42);

        drawables[Status.LOADING].setBorderWidth(20);

        // 設定重繪回撥
        drawables[Status.LOADING].setCallback(this);
    }

    /**
     * 動畫時長
     */
    private int mDuration = 500;

    /**
     * 目前的狀態t
     */
    private int mCurrentStatus = Status.NORMAL;

    /**
     * 是否正在動畫中
     */
    private boolean isAnim;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 這裡設定一些初始值
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        drawables[Status.NORMAL].setWidth(width);
        drawables[Status.NORMAL].setHeight(height);
        drawables[Status.LOADING].setWidth(width);
        drawables[Status.LOADING].setHeight(height);
        drawables[Status.ERROR].setWidth(width);
        drawables[Status.ERROR].setHeight(height);
        drawables[Status.COMPLETE].setWidth(width);
        drawables[Status.COMPLETE].setHeight(height);
        // 設定的Radius
        int radius = width > height ? height / 2 : width / 2;
        drawables[Status.NORMAL].setMinSize(radius * 2);
        drawables[Status.LOADING].setMinSize(radius * 2);
        drawables[Status.ERROR].setMinSize(radius * 2);
        drawables[Status.COMPLETE].setMinSize(radius * 2);
        drawables[Status.LOADING].getInfo().setRadius(radius);
        drawables[Status.LOADING].getInfo().setPadding(width > height ? (width - radius * 2) / 2 : (height - radius * 2) / 2);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 畫出不同狀態的內容
        drawables[mCurrentStatus].draw(canvas);
    }

    /**
     * 設定狀態
     */
    public void setStatus(int status) {
        if (mCurrentStatus != status && !isAnim) {
            // 這裡設定動畫效果
            changeStatus(mCurrentStatus, status);
            // 釋放之前的動畫
            this.drawables[mCurrentStatus].release();
            this.mCurrentStatus = status;
            this.drawables[mCurrentStatus].setIsAnim(true);
        }
    }

    /**
     * 狀態改變的動畫
     */
    private void changeStatus(int fromStatus, int toStatus) {
        isAnim = true;
        // 取出相關的動畫資訊
        CircleProgressSuperInfo fromStatusInfo = drawables[fromStatus].getInfo();
        CircleProgressSuperInfo toStatusInfo = drawables[toStatus].getInfo();
        // 開始動畫
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(mDuration);
        animatorSet.playTogether(AnimUtil.getColorAnim(fromStatusInfo.getBgColor(), toStatusInfo.getBgColor(), mDuration, colorUpdateListener),
                AnimUtil.getColorAnim(fromStatusInfo.getBorderColor(), toStatusInfo.getBorderColor(), mDuration, borderColorUpdateListener),
                AnimUtil.getRadiusAnim(fromStatusInfo.getRadius(), toStatusInfo.getRadius(), mDuration, radiusUpdateListener),
                AnimUtil.getShapeAnim(fromStatusInfo.getPadding(), toStatusInfo.getPadding(), mDuration, shapeUpdateListener)
        );
        animatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                isAnim = false;
                drawables[mCurrentStatus].setIsAnim(false);
            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        animatorSet.start();
    }

    /**
     * color動畫的回撥
     */
    private ValueAnimator.AnimatorUpdateListener colorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            drawables[mCurrentStatus].setBgColor((Integer) valueAnimator.getAnimatedValue());
            invalidate();
        }
    };

    /**
     * borderColor動畫的回撥
     */
    private ValueAnimator.AnimatorUpdateListener borderColorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            drawables[mCurrentStatus].setBorderColor((Integer) valueAnimator.getAnimatedValue());
        }
    };


    /**
     * radius動畫的回撥
     */
    private ValueAnimator.AnimatorUpdateListener radiusUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            drawables[mCurrentStatus].setRadius((float) valueAnimator.getAnimatedValue());
        }
    };

    /**
     * shape動畫的回撥
     */
    private ValueAnimator.AnimatorUpdateListener shapeUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            drawables[mCurrentStatus].setPadding((Float) valueAnimator.getAnimatedValue());
        }
    };


    /**
     * Invalidates the specified Drawable.
     *
     * @param drawable the drawable to invalidate
     */
    @Override
    public void invalidateDrawable(@NonNull Drawable drawable) {
        invalidate();
    }

}

主要是找到各個狀態的BaseStatusDrawable,然後取出資訊,開始屬性動畫,但是有幾個小知識點,你還記得嗎?

1、在獲取指定狀態的圖片,直接使用Status.xxx作為陣列的索引,儲存和取出的速度都很快,是不是想起之前我們聊過的雜湊表了?

2、onMeasure方法裡,記得使用getMeasuredXXX,因為getWidth和getHeight都是0,千萬別忘了。

有些朋友發現了:怎麼突然冒出來一個invalidateDrawable()方法?我這裡直說了,大家想自己去踩坑的可以試試:

還記得之前說過的繼承Drawable是為了重繪嗎,如果是你只是呼叫了Drawable.invalidateSelf(),很遺憾的告訴你,是不可能重繪的,所以這裡要重寫這個方法,強制重繪。

直接看原始碼就知道原因了:

//Drawable的重繪方法,實際上是呼叫了callback,這樣就和View解耦了
public void invalidateSelf() {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.invalidateDrawable(this);
        }
    }

// view本身就實現了Callback
public class View implements Drawable.Callback{

    // 請注意裡面的判斷
    @Override
    public void invalidateDrawable(@NonNull Drawable drawable) {
      // 滿足了這個條件,才會重繪,所以要看看判斷條件是什麼
        if (verifyDrawable(drawable)) {
            final Rect dirty = drawable.getDirtyBounds();
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;

            invalidate(dirty.left + scrollX, dirty.top + scrollY,
                    dirty.right + scrollX, dirty.bottom + scrollY);
            rebuildOutline();
        }
    }

@CallSuper
    protected boolean verifyDrawable(@NonNull Drawable who) {
    // 這裡就是判斷,view要判斷是否使用了這個Drawable,如果沒有使用,就不去重繪了,這個理論都是可以理解的。
        return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who);
    }
}

因為我們僅僅是canvas繪圖,並沒有設定背景或者是前景圖片之類的東西,自然就無法重繪,也就是重寫invalidateDrawable()方法的原因。

還有兩個類,沒有貼出來:Status(Drawable的狀態),AnimUtil(動畫工具類),因為感覺今天的內容已經很長了,所以就省略了把,大家可以在demo中去檢視。

總結

看的說的挺溜,其實在寫的時候還是出現了很多問題的,而且現在也還存在一些小問題,如果你發現部落格中的程式碼和demo中有一點點區別,那就是我後來又修改了,但是主要思想是不會變了,大家可以自己去設定自定義屬性,這樣我們的完成度就更完美了。