Android動(dòng)畫(huà)篇(四):最終效果篇CircleProgressSuperBar

前言#

今天終于有時(shí)間把最后的成果分享給大家了,為了提高一下博客的逼格,我也找了一個(gè)專門(mén)做原型、導(dǎo)圖的在線網(wǎng)站:processon(www.processon.com,這個(gè)工具真的很棒,也很方便,這里給他點(diǎn)個(gè)贊。

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

這里寫(xiě)圖片描述

正文#

首先我們來(lái)分析一下,CircleProgressSuperBar總共分為幾種狀態(tài):

這里寫(xiě)圖片描述

這四種狀態(tài),我們不關(guān)心是怎么切換的,我們只關(guān)心動(dòng)畫(huà)是怎么過(guò)渡的。

首先我們知道準(zhǔn)備一些類(lèi):

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

最終為了方便擴(kuò)展和解耦,我最終實(shí)現(xiàn)的架構(gòu)圖是這樣的:

這里寫(xiě)圖片描述

我的主要目的:

1、在View和Drawable之間創(chuàng)建工廠類(lèi),即能降低類(lèi)之間的耦合,也可以方便擴(kuò)展更多狀態(tài)的Drawable。

2、各種狀態(tài)Drawble的基類(lèi)BaseStatusDrawable,封裝公共的屬性和方法,讓View直接使用BaseStatusDrawable類(lèi)型,而不去具體關(guān)心具體的Drawable的實(shí)現(xiàn)。

3、BaseStatusDrawable內(nèi)部帶有樣式的信息,防止和內(nèi)部的畫(huà)筆有關(guān)的顏色弄混。

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

public class CircleProgressSuperInfo {

    /**
     * 寬, 在設(shè)置動(dòng)畫(huà)的時(shí)候需要知道寬
     */
    private int mWidth;

    /**
     * 高,在設(shè)置動(dòng)畫(huà)的時(shí)候需要知道高
     */
    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,我們直接把之前寫(xiě)好的形狀變化的ChangeShapeAndColorButton進(jìn)行改造,變成我們需要的BaseStatusDrawable:

/**
 * Created by li.zhipeng on 2017/7/12.
 * <p>
 * 所有的狀態(tài)圖片需要實(shí)現(xiàn)此接口
 */
public abstract class BaseStatusDrawable extends Drawable {

    protected CircleProgressSuperInfo mInfo;

    /**
     * 寬, 在設(shè)置動(dòng)畫(huà)的時(shí)候需要知道寬
     */
    protected int mWidth;

    /**
     * 高,在設(shè)置動(dòng)畫(huà)的時(shí)候需要知道高
     */
    protected int mHeight;

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

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

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

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

    /**
     * 畫(huà)筆
     */
    protected Paint mPaint;

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


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

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

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

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

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

    /**
     * 正在動(dòng)畫(huà)在中
     */
    protected boolean isAnim;

    public BaseStatusDrawable(CircleProgressSuperInfo info) {
        this.mInfo = info;
        // 初始化畫(huà)筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setDither(true);
        mRectF = new RectF();
    }
    
    // 此處省略各種setter和getter方法
    ...

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

    /**
     * 回到后臺(tái),調(diào)用此方法
     */
    public abstract void release();

    /**
     * 繪制
     */
    @Override
    public void draw(@NonNull Canvas canvas) {
        // 這是繪制過(guò)渡動(dòng)畫(huà)
        if (isAnim()) {
            drawTransition(canvas);
        }
        // 繪制正常狀態(tài),例如loading就要使用繪制旋轉(zhuǎn)的圓圈
        else {
            drawSelf(canvas);
        }
    }

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

        // 計(jì)算左右的間距值,并且判斷不能小于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);

        // 開(kāi)始畫(huà)后面的背景
        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);
        }
    }


    /**
     * 繪制正常狀態(tài)
     */
    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;
    }
}

這里要強(qiáng)調(diào)幾點(diǎn):

1、繼承Drawable的原因:主要是為了重繪,例如之后的加載狀態(tài),是需要不斷重繪的才會(huì)有轉(zhuǎn)圈的動(dòng)畫(huà),但是已經(jīng)和View分離了,就無(wú)法借助View.invaliate(),所以這里繼承了Drawable。

2、drawSelf()是繪制非過(guò)渡動(dòng)畫(huà)的狀態(tài),這里主要是給LoadingDrawable用的。

3、getOpacity()方法的作用:返回圖片的質(zhì)量類(lèi)型。

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

// 截取的注釋,getOpacity值能返回一下幾個(gè)值
PixelFormat.UNKNOWN
PixelFormat.TRANSLUCENT
PixelFormat.TRANSPARENT
PixelFormat.OPAQUE

// 系統(tǒng)自動(dòng)適配
public static final int UNKNOWN     = 0;

// 簡(jiǎn)單的說(shuō)就是支持半透明
public static final int TRANSLUCENT = -3;

// 支持全透明
public static final int TRANSPARENT = -2;
    
// 不支持透明
public static final int OPAQUE      = -1;

屬性的命名規(guī)則就是見(jiàn)字如見(jiàn)人,字面意思就是這樣。其他兩個(gè)方法,這里沒(méi)有用到就不說(shuō)明了,有興趣的可以自己去研究研究。

這個(gè)時(shí)候去就可以創(chuàng)建四個(gè)狀態(tài)的Drawable了,普通狀態(tài)、完成狀態(tài)還有錯(cuò)誤狀態(tài)都是一樣的,所以就只貼一個(gè)類(lèi)的代碼了:

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

public class NormalDrawable extends BaseStatusDrawable {

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

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

    @Override
    public void release() {

    }
    
}

重點(diǎn)是LoadingDrawable,其實(shí)也很簡(jiǎn)單,因?yàn)槔^承的關(guān)系,現(xiàn)在只需要關(guān)心繪制加載狀態(tài)就足夠了,這個(gè)時(shí)候把我們第一篇CircleProgressBar進(jìn)行改造:

/**
 * Created by li.zhipeng on 2017/7/12.
 * <p>
 * 加載狀態(tài)或是進(jìn)度條的drawable
 */

public class LoadingDrawable extends BaseStatusDrawable {

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

    /**
     * 進(jìn)度
     */
    private float mProgress = 50;

    /**
     * 最大進(jìn)度
     */
    private int mMaxProgress = 100;

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

    /**
     * 繪制的不全進(jìn)度的顏色
     */
    private int mDrawBorderColor;

    /**
     * 要繪制的進(jìn)度條的顏色
     */
    private int mDrawProgressColor;

    /**
     * 是否打開(kāi)過(guò)度模式,也就是我們平時(shí)看到的類(lèi)似追趕的效果
     */
    private boolean mIsIntermediateMode = true;

    /**
     * 最小弧度,進(jìn)度條過(guò)度模式最小的弧度
     */
    private int mMinProgress = 5;

    /**
     * 過(guò)度動(dòng)畫(huà)的時(shí)間
     */
    private static final int DURATION = 1000;

    /**
     * 過(guò)度動(dòng)畫(huà)
     */
    private ValueAnimator valueAnimator;

    /**
     * 開(kāi)始角度,在過(guò)度動(dòng)畫(huà)中使用
     */
    private float mStartAngle = -90f;

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

    /**
     * 設(shè)置進(jìn)度
     */
    public void setProgress(float progress) {
        this.mProgress = progress;
    }

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

    /**
     * 設(shè)置進(jìn)度條的顏色
     */
    public void setProgressColor(int color) {
        this.mProgressColor = color;
    }

    /**
     * 設(shè)置進(jìn)度條的顏色
     */
    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;
    }

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

    /**
     * 設(shè)置繪制區(qū)域
     */
    @Override
    public void setRadius(float radius) {
        super.setRadius(radius);
        // 計(jì)算要繪制的區(qū)域
        mRectF.set(mWidth / 2 - mRadius + mBorderWidth / 2, mHeight / 2 - mRadius + mBorderWidth / 2,
                mWidth / 2 + mRadius - mBorderWidth / 2, mHeight / 2 + mRadius - mBorderWidth / 2);
    }

    /**
     * 設(shè)置loading模式
     */
    public void setIntermediateMode(boolean intermediateMode) {
        if (mIsIntermediateMode != intermediateMode) {
            this.mIsIntermediateMode = intermediateMode;
            // 取消動(dòng)畫(huà)
            if (!mIsIntermediateMode) {
                valueAnimator.cancel();
            } else {
                //這里要開(kāi)啟動(dòng)畫(huà)
                startIntermediateAnim();
            }
        }
    }

    @Override
    public void drawSelf(Canvas canvas) {
        // 是否要顯示loading狀態(tài)
        if (mIsIntermediateMode) {
            startIntermediateAnim();
            drawIntermediateProgress(canvas);
        }
        // 繪制進(jìn)度條
        else {
            drawProgress(canvas);
        }
    }

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

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

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

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

    /**
     * 開(kāi)始過(guò)度動(dòng)畫(huà)
     */
    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) {
                    // 充值旋轉(zhuǎn)的角度
                    mStartAngle = -90;
                }

                @Override
                public void onAnimationRepeat(Animator animator) {
                    // 互換顏色和位置
                    mStartAngle = mStartAngle - CIRCULAR / mMaxProgress * mMinProgress;
                    int color = getDrawProgressColor();
                    setDrawProgressColor(getDrawBorderColor());
                    setDrawBorderColor(color);
                }
            });
        }
        // 開(kāi)始動(dòng)畫(huà)的時(shí)候,要重新設(shè)置顏色,否則顏色可能會(huì)錯(cuò)亂,因?yàn)樵趧?dòng)畫(huà)的過(guò)程已經(jīng)互換
        setDrawProgressColor(mProgressColor);
        setDrawBorderColor(mBorderColor);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.start();
    }

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

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

}

幾乎是沒(méi)有什么變化,增加了開(kāi)始動(dòng)畫(huà)和結(jié)束動(dòng)畫(huà)方法,這樣其他的狀態(tài)時(shí),可以節(jié)省系統(tǒng)資源。

然后是工廠類(lèi):

/**
 * Created by li.zhipeng on 2017/7/13.
 * <p>
 * 生產(chǎn)不同狀態(tài)的Drawable的生產(chǎn)類(lèi)
 */

public class StatusDrawableFactory {

    private static StatusDrawableFactory mInstance;

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

    /**
     * 返回指定狀態(tài)的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;
    }

}

非常簡(jiǎn)單的單例模式,返回指定的BaseStatusDrawable類(lèi)型。

最后就是CircleProgressSuperBar:

/**
 * Created by li.zhipeng on 2017/7/12.
 * <p>
 * 具有多狀態(tài)的CircleProgressBar,整合前兩個(gè)控件的效果
 */

public class CircleProgressSuperBar extends View {

    /**
     * 保存四張狀態(tài)的Drawable
     */
    private BaseStatusDrawable[] drawables = new BaseStatusDrawable[4];

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

        // 初始化信息類(lèi)
        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);

        // 設(shè)置重繪回調(diào)
        drawables[Status.LOADING].setCallback(this);
    }

    /**
     * 動(dòng)畫(huà)時(shí)長(zhǎng)
     */
    private int mDuration = 500;

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

    /**
     * 是否正在動(dòng)畫(huà)中
     */
    private boolean isAnim;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 這里設(shè)置一些初始值
        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);
        // 設(shè)置的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);
        // 畫(huà)出不同狀態(tài)的內(nèi)容
        drawables[mCurrentStatus].draw(canvas);
    }

    /**
     * 設(shè)置狀態(tài)
     */
    public void setStatus(int status) {
        if (mCurrentStatus != status && !isAnim) {
            // 這里設(shè)置動(dòng)畫(huà)效果
            changeStatus(mCurrentStatus, status);
            // 釋放之前的動(dòng)畫(huà)
            this.drawables[mCurrentStatus].release();
            this.mCurrentStatus = status;
            this.drawables[mCurrentStatus].setIsAnim(true);
        }
    }

    /**
     * 狀態(tài)改變的動(dòng)畫(huà)
     */
    private void changeStatus(int fromStatus, int toStatus) {
        isAnim = true;
        // 取出相關(guān)的動(dòng)畫(huà)信息
        CircleProgressSuperInfo fromStatusInfo = drawables[fromStatus].getInfo();
        CircleProgressSuperInfo toStatusInfo = drawables[toStatus].getInfo();
        // 開(kāi)始動(dòng)畫(huà)
        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動(dòng)畫(huà)的回調(diào)
     */
    private ValueAnimator.AnimatorUpdateListener colorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            drawables[mCurrentStatus].setBgColor((Integer) valueAnimator.getAnimatedValue());
            invalidate();
        }
    };

    /**
     * borderColor動(dòng)畫(huà)的回調(diào)
     */
    private ValueAnimator.AnimatorUpdateListener borderColorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            drawables[mCurrentStatus].setBorderColor((Integer) valueAnimator.getAnimatedValue());
        }
    };


    /**
     * radius動(dòng)畫(huà)的回調(diào)
     */
    private ValueAnimator.AnimatorUpdateListener radiusUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            drawables[mCurrentStatus].setRadius((float) valueAnimator.getAnimatedValue());
        }
    };

    /**
     * shape動(dòng)畫(huà)的回調(diào)
     */
    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();
    }

}

主要是找到各個(gè)狀態(tài)的BaseStatusDrawable,然后取出信息,開(kāi)始屬性動(dòng)畫(huà),但是有幾個(gè)小知識(shí)點(diǎn),你還記得嗎?

1、在獲取指定狀態(tài)的圖片,直接使用Status.xxx作為數(shù)組的索引,保存和取出的速度都很快,是不是想起之前我們聊過(guò)的哈希表了?

2、onMeasure方法里,記得使用getMeasuredXXX,因?yàn)間etWidth和getHeight都是0,千萬(wàn)別忘了。

有些朋友發(fā)現(xiàn)了:怎么突然冒出來(lái)一個(gè)invalidateDrawable()方法?我這里直說(shuō)了,大家想自己去踩坑的可以試試:

還記得之前說(shuō)過(guò)的繼承Drawable是為了重繪嗎,如果是你只是調(diào)用了Drawable.invalidateSelf(),很遺憾的告訴你,是不可能重繪的,所以這里要重寫(xiě)這個(gè)方法,強(qiáng)制重繪。

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

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

// view本身就實(shí)現(xiàn)了Callback
public class View implements Drawable.Callback{

    // 請(qǐng)注意里面的判斷
    @Override
    public void invalidateDrawable(@NonNull Drawable drawable) {
      // 滿足了這個(gè)條件,才會(huì)重繪,所以要看看判斷條件是什么
        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要判斷是否使用了這個(gè)Drawable,如果沒(méi)有使用,就不去重繪了,這個(gè)理論都是可以理解的。
        return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who);
    }
}

因?yàn)槲覀儍H僅是canvas繪圖,并沒(méi)有設(shè)置背景或者是前景圖片之類(lèi)的東西,自然就無(wú)法重繪,也就是重寫(xiě)invalidateDrawable()方法的原因。

還有兩個(gè)類(lèi),沒(méi)有貼出來(lái):Status(Drawable的狀態(tài)),AnimUtil(動(dòng)畫(huà)工具類(lèi)),因?yàn)楦杏X(jué)今天的內(nèi)容已經(jīng)很長(zhǎng)了,所以就省略了把,大家可以在demo中去查看。

總結(jié)#

看的說(shuō)的挺溜,其實(shí)在寫(xiě)的時(shí)候還是出現(xiàn)了很多問(wèn)題的,而且現(xiàn)在也還存在一些小問(wèn)題,如果你發(fā)現(xiàn)博客中的代碼和demo中有一點(diǎn)點(diǎn)區(qū)別,那就是我后來(lái)又修改了,但是主要思想是不會(huì)變了,大家可以自己去設(shè)置自定義屬性,這樣我們的完成度就更完美了。

github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容