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中有一点点区别,那就是我后来又修改了,但是主要思想是不会变了,大家可以自己去设置自定义属性,这样我们的完成度就更完美了。

github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容