Android值Drawable系列:
一起来说说那些你不知道的Drawable:http://blog.csdn.net/mr_dsw/article/details/50998681
Android实践之Drawable的使用:http://blog.csdn.net/mr_dsw/article/details/50999818
开源项目circular-progress-button源码解析:http://blog.csdn.net/mr_dsw/article/details/51253844
在这几篇文章中,我们介绍了Drawable系列的基本成员以及基本的使用,这次我们以circular-progress-button开源项目为案例进行分析,这个开源项目里涉及到Drawable的使用,所以是一个不错分析案例。
一、项目概述
这个项目实现了一套精美的按钮过渡效果:
是不是很靓丽。我们打开项目工程,查看这个项目工程的结构:
其中:
1.CircularProgressButton类:是我们实现的自定义控件,该控件继承Button实现。
2.CircularAnimatedDrawable类:实现弧长变化效果的Drawable。
3.CircularProgressDrawable类:实现圆形进度的Drawable。
4.MorphingAnimation类:里面封装属性动画实现Button不同效果之间的切换。
5.StateManager:控件状态管理类
6.StrokeGradientDrawable:渐变的Drawable管理类
下面我们就分别研究下所实现的原理。
二、具体类实现技术研究:
1、CircularProgressDrawable类
public class CircularProgressDrawable extends Drawable { private float mSweepAngle; private float mStartAngle; private int mSize; private int mStrokeWidth; private int mStrokeColor; public CircularProgressDrawable(int size, int strokeWidth, int strokeColor) { mSize = size; mStrokeWidth = strokeWidth; mStrokeColor = strokeColor; mStartAngle = -90; mSweepAngle = 0; } public void setSweepAngle(float sweepAngle) { mSweepAngle = sweepAngle; } public int getSize() { return mSize; } @Override public void draw(Canvas canvas) { final Rect bounds = getBounds(); if (mPath == null) { mPath = new Path(); } mPath.reset(); mPath.addArc(getRect(), mStartAngle, mSweepAngle); mPath.offset(bounds.left, bounds.top); canvas.drawPath(mPath, createPaint()); } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter cf) { } @Override public int getOpacity() { return 1; } private RectF mRectF; private Paint mPaint; private Path mPath; private RectF getRect() { if (mRectF == null) { int index = mStrokeWidth / 2; mRectF = new RectF(index, index, getSize() - index, getSize() - index); } return mRectF; } private Paint createPaint() { if (mPaint == null) { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mStrokeWidth); mPaint.setColor(mStrokeColor); } return mPaint; } }
这是一个绘制圆形的自定义Drawable。首先通过CircularProgressDrawable构造方法我们指定圆形的描边宽度和描边颜色,同时初始化圆弧绘制的起始角度为-90和绘制角度0。我们通过setSweepAngle(float sweepAngle)方法设置圆弧绘制的角度,通过createPaint()初始化我们的画笔mPaint对象,通过getRect()方法指定绘制圆弧所在的矩形范围(通过size和描边确定)。最后我们在draw()方法中进行圆弧的绘制。这样,我们就实现了一个圆形进度的Drawable对象的开发。
2、CircularAnimatedDrawable类
public class CircularAnimatedDrawable extends Drawable implements Animatable { private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator(); private static final Interpolator SWEEP_INTERPOLATOR = new DecelerateInterpolator(); private static final int ANGLE_ANIMATOR_DURATION = 2000; private static final int SWEEP_ANIMATOR_DURATION = 600; public static final int MIN_SWEEP_ANGLE = 30; private final RectF fBounds = new RectF(); private ObjectAnimator mObjectAnimatorSweep; private ObjectAnimator mObjectAnimatorAngle; private boolean mModeAppearing; private Paint mPaint; private float mCurrentGlobalAngleOffset; private float mCurrentGlobalAngle; private float mCurrentSweepAngle; private float mBorderWidth; private boolean mRunning; public CircularAnimatedDrawable(int color, float borderWidth) { mBorderWidth = borderWidth; mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(borderWidth); mPaint.setColor(color); setupAnimations(); } @Override public void draw(Canvas canvas) { float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset; float sweepAngle = mCurrentSweepAngle; if (!mModeAppearing) { startAngle = startAngle + sweepAngle; sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE; } else { sweepAngle += MIN_SWEEP_ANGLE; } canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint); } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSPARENT; } private void toggleAppearingMode() { mModeAppearing = !mModeAppearing; if (mModeAppearing) { mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360; } } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); fBounds.left = bounds.left + mBorderWidth / 2f + .5f; fBounds.right = bounds.right - mBorderWidth / 2f - .5f; fBounds.top = bounds.top + mBorderWidth / 2f + .5f; fBounds.bottom = bounds.bottom - mBorderWidth / 2f - .5f; } private Property<CircularAnimatedDrawable, Float> mAngleProperty = new Property<CircularAnimatedDrawable, Float>(Float.class, "angle") { @Override public Float get(CircularAnimatedDrawable object) { return object.getCurrentGlobalAngle(); } @Override public void set(CircularAnimatedDrawable object, Float value) { object.setCurrentGlobalAngle(value); } }; private Property<CircularAnimatedDrawable, Float> mSweepProperty = new Property<CircularAnimatedDrawable, Float>(Float.class, "arc") { @Override public Float get(CircularAnimatedDrawable object) { return object.getCurrentSweepAngle(); } @Override public void set(CircularAnimatedDrawable object, Float value) { object.setCurrentSweepAngle(value); } }; private void setupAnimations() { mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f); mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR); mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION); mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART); mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE); mObjectAnimatorSweep = ObjectAnimator.ofFloat(this, mSweepProperty, 360f - MIN_SWEEP_ANGLE * 2); mObjectAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR); mObjectAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION); mObjectAnimatorSweep.setRepeatMode(ValueAnimator.RESTART); mObjectAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE); mObjectAnimatorSweep.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { toggleAppearingMode(); } }); } @Override public void start() { if (isRunning()) { return; } mRunning = true; mObjectAnimatorAngle.start(); mObjectAnimatorSweep.start(); invalidateSelf(); } @Override public void stop() { if (!isRunning()) { return; } mRunning = false; mObjectAnimatorAngle.cancel(); mObjectAnimatorSweep.cancel(); invalidateSelf(); } @Override public boolean isRunning() { return mRunning; } public void setCurrentGlobalAngle(float currentGlobalAngle) { mCurrentGlobalAngle = currentGlobalAngle; invalidateSelf(); } public float getCurrentGlobalAngle() { return mCurrentGlobalAngle; } public void setCurrentSweepAngle(float currentSweepAngle) { mCurrentSweepAngle = currentSweepAngle; invalidateSelf(); } public float getCurrentSweepAngle() { return mCurrentSweepAngle; } }
从类的声明上来看,CircularAnimatedDrawable类与CircularProgressDrawable的不同之处在于CircularAnimatedDrawable实现了Animatable接口。我们都知道这个Animatable接口就是为了支持Drawable实现动画的,所以这个CircularAnimatedDrawable肯定会有动画效果,即旋转的弧形效果。
首先我们从该类的成员变量分析,首先是定义的一些基本常量如:圆弧角度变化的加速器、持续时间、最小旋转角度等。同时也定义了一些常用的变量,比如:圆弧角度的变化对应的Animator、当前的角度(mCurrentGlobalAngle)、圆弧旋转角度(mCurrentSweepAngle)、角度偏移量(mCurrentGlobalAngleOffset)、圆弧宽度等。主题思路就是我们通过属性动画对圆弧的角度进行变化,然后进行绘制,形成动态的圆弧效果。在这里,我们需要着重较少下Property类型的两个成员变量mAngleProperty、mSweepProperty,它封装了我们的角度和旋转角度,我们就是通过Property类进行封装类的属性,然后进行属性动画变换(属性动画一般要求属性具有get、set方法)。
成员变量基本介绍完毕,这里我们回头去看构造函数,着重注意方法setupAnimations(),这个方法用于初始化属性动画类,指定相关的属性。这里注意一点,我们通过Property封装CircularAnimatedDrawable对象,然后进行设置角度,实现角度的改变,达到效果。最后在draw方法中进行绘制。这里的一个难点就是实现这个效果的一套角度的逻辑处理,需要仔细研究下。
3、CircularProgressButton类
这个类就是我们的核心控件类,我们通过该类实现效果。在我们实际开发中,我们通过selector文件给Button设置不同状态下的样式。同样,这个的实现原理也是通过状态进行判断,针对不同状态之间的切换配以动画的处理,所以显得无比的靓丽。
public class CircularProgressButton extends Button { //Button的状态值 public static final int IDLE_STATE_PROGRESS = 0; public static final int ERROR_STATE_PROGRESS = -1; public static final int SUCCESS_STATE_PROGRESS = 100; public static final int INDETERMINATE_STATE_PROGRESS = 50; private StrokeGradientDrawable background; //切换的两个Drawable private CircularAnimatedDrawable mAnimatedDrawable; private CircularProgressDrawable mProgressDrawable; //定义不同状态下的字体颜色ColorStateList private ColorStateList mIdleColorState; private ColorStateList mCompleteColorState; private ColorStateList mErrorColorState; //Button对应的不同状态的Drawable private StateListDrawable mIdleStateDrawable; private StateListDrawable mCompleteStateDrawable; private StateListDrawable mErrorStateDrawable; //状态管理器 private StateManager mStateManager; private State mState; //状态 private String mIdleText; //Idle状态下的文字 private String mCompleteText; //Complete时的文字 private String mErrorText; //Error时的文字 private String mProgressText; //进度条文字 private int mColorProgress; private int mColorIndicator; private int mColorIndicatorBackground; private int mIconComplete; private int mIconError; private int mStrokeWidth; private int mPaddingProgress; private float mCornerRadius; private boolean mIndeterminateProgressMode; private boolean mConfigurationChanged; //枚举类型的状态 private enum State { PROGRESS, IDLE, COMPLETE, ERROR } private int mMaxProgress; private int mProgress; private boolean mMorphingInProgress; public CircularProgressButton(Context context) { super(context); init(context, null); } public CircularProgressButton(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public CircularProgressButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs); } //用于初始化相关的成员变量 private void init(Context context, AttributeSet attributeSet) { mStrokeWidth = (int) getContext().getResources().getDimension(R.dimen.cpb_stroke_width); initAttributes(context, attributeSet); mMaxProgress = 100; mState = State.IDLE; mStateManager = new StateManager(this); setText(mIdleText); //状态初始化为Idle静止状态 initIdleStateDrawable(); setBackgroundCompat(mIdleStateDrawable); } //设置为Error状态 private void initErrorStateDrawable() { int colorPressed = getPressedColor(mErrorColorState); StrokeGradientDrawable drawablePressed = createDrawable(colorPressed); mErrorStateDrawable = new StateListDrawable(); //给ErrorStateDrawable添加状态。 mErrorStateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed.getGradientDrawable()); mErrorStateDrawable.addState(StateSet.WILD_CARD, background.getGradientDrawable()); } //初始化Complete状态的StateListDrawable private void initCompleteStateDrawable() { int colorPressed = getPressedColor(mCompleteColorState); StrokeGradientDrawable drawablePressed = createDrawable(colorPressed); mCompleteStateDrawable = new StateListDrawable(); mCompleteStateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed.getGradientDrawable()); mCompleteStateDrawable.addState(StateSet.WILD_CARD, background.getGradientDrawable()); } private void initIdleStateDrawable() { int colorNormal = getNormalColor(mIdleColorState); int colorPressed = getPressedColor(mIdleColorState); int colorFocused = getFocusedColor(mIdleColorState); int colorDisabled = getDisabledColor(mIdleColorState); if (background == null) { background = createDrawable(colorNormal); } StrokeGradientDrawable drawableDisabled = createDrawable(colorDisabled); StrokeGradientDrawable drawableFocused = createDrawable(colorFocused); StrokeGradientDrawable drawablePressed = createDrawable(colorPressed); mIdleStateDrawable = new StateListDrawable(); mIdleStateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed.getGradientDrawable()); mIdleStateDrawable.addState(new int[]{android.R.attr.state_focused}, drawableFocused.getGradientDrawable()); mIdleStateDrawable.addState(new int[]{-android.R.attr.state_enabled}, drawableDisabled.getGradientDrawable()); mIdleStateDrawable.addState(StateSet.WILD_CARD, background.getGradientDrawable()); } //获取正常状态下的ColorStateList private int getNormalColor(ColorStateList colorStateList) { return colorStateList.getColorForState(new int[]{android.R.attr.state_enabled}, 0); } private int getPressedColor(ColorStateList colorStateList) { return colorStateList.getColorForState(new int[]{android.R.attr.state_pressed}, 0); } private int getFocusedColor(ColorStateList colorStateList) { return colorStateList.getColorForState(new int[]{android.R.attr.state_focused}, 0); } private int getDisabledColor(ColorStateList colorStateList) { return colorStateList.getColorForState(new int[]{-android.R.attr.state_enabled}, 0); } private StrokeGradientDrawable createDrawable(int color) { GradientDrawable drawable = (GradientDrawable) getResources().getDrawable(R.drawable.cpb_background).mutate(); drawable.setColor(color); drawable.setCornerRadius(mCornerRadius); StrokeGradientDrawable strokeGradientDrawable = new StrokeGradientDrawable(drawable); strokeGradientDrawable.setStrokeColor(color); strokeGradientDrawable.setStrokeWidth(mStrokeWidth); return strokeGradientDrawable; } //关键点,重写drawableStateChanged方法,判断改变的状态,然后进行不同状态的切换 @Override protected void drawableStateChanged() { if (mState == State.COMPLETE) { initCompleteStateDrawable(); setBackgroundCompat(mCompleteStateDrawable); } else if (mState == State.IDLE) { initIdleStateDrawable(); setBackgroundCompat(mIdleStateDrawable); } else if (mState == State.ERROR) { initErrorStateDrawable(); setBackgroundCompat(mErrorStateDrawable); } if (mState != State.PROGRESS) { super.drawableStateChanged(); } } private void initAttributes(Context context, AttributeSet attributeSet) { TypedArray attr = getTypedArray(context, attributeSet, R.styleable.CircularProgressButton); if (attr == null) { return; } try { mIdleText = attr.getString(R.styleable.CircularProgressButton_cpb_textIdle); mCompleteText = attr.getString(R.styleable.CircularProgressButton_cpb_textComplete); mErrorText = attr.getString(R.styleable.CircularProgressButton_cpb_textError); mProgressText = attr.getString(R.styleable.CircularProgressButton_cpb_textProgress); mIconComplete = attr.getResourceId(R.styleable.CircularProgressButton_cpb_iconComplete, 0); mIconError = attr.getResourceId(R.styleable.CircularProgressButton_cpb_iconError, 0); mCornerRadius = attr.getDimension(R.styleable.CircularProgressButton_cpb_cornerRadius, 0); mPaddingProgress = attr.getDimensionPixelSize(R.styleable.CircularProgressButton_cpb_paddingProgress, 0); int blue = getColor(R.color.cpb_blue); int white = getColor(R.color.cpb_white); int grey = getColor(R.color.cpb_grey); int idleStateSelector = attr.getResourceId(R.styleable.CircularProgressButton_cpb_selectorIdle, R.color.cpb_idle_state_selector); mIdleColorState = getResources().getColorStateList(idleStateSelector); int completeStateSelector = attr.getResourceId(R.styleable.CircularProgressButton_cpb_selectorComplete, R.color.cpb_complete_state_selector); mCompleteColorState = getResources().getColorStateList(completeStateSelector); int errorStateSelector = attr.getResourceId(R.styleable.CircularProgressButton_cpb_selectorError, R.color.cpb_error_state_selector); mErrorColorState = getResources().getColorStateList(errorStateSelector); mColorProgress = attr.getColor(R.styleable.CircularProgressButton_cpb_colorProgress, white); mColorIndicator = attr.getColor(R.styleable.CircularProgressButton_cpb_colorIndicator, blue); mColorIndicatorBackground = attr.getColor(R.styleable.CircularProgressButton_cpb_colorIndicatorBackground, grey); } finally { attr.recycle(); } } protected int getColor(int id) { return getResources().getColor(id); } protected TypedArray getTypedArray(Context context, AttributeSet attributeSet, int[] attr) { return context.obtainStyledAttributes(attributeSet, attr, 0, 0); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mProgress > 0 && mState == State.PROGRESS && !mMorphingInProgress) { if (mIndeterminateProgressMode) {//绘制可变弧长形式 drawIndeterminateProgress(canvas); } else {//绘制纯进度条形式 drawProgress(canvas); } } } private void drawIndeterminateProgress(Canvas canvas) { if (mAnimatedDrawable == null) { int offset = (getWidth() - getHeight()) / 2; mAnimatedDrawable = new CircularAnimatedDrawable(mColorIndicator, mStrokeWidth); int left = offset + mPaddingProgress; int right = getWidth() - offset - mPaddingProgress; int bottom = getHeight() - mPaddingProgress; int top = mPaddingProgress; mAnimatedDrawable.setBounds(left, top, right, bottom); mAnimatedDrawable.setCallback(this); mAnimatedDrawable.start(); } else { mAnimatedDrawable.draw(canvas); } } private void drawProgress(Canvas canvas) { if (mProgressDrawable == null) { int offset = (getWidth() - getHeight()) / 2; int size = getHeight() - mPaddingProgress * 2; mProgressDrawable = new CircularProgressDrawable(size, mStrokeWidth, mColorIndicator); int left = offset + mPaddingProgress; mProgressDrawable.setBounds(left, mPaddingProgress, left, mPaddingProgress); } float sweepAngle = (360f / mMaxProgress) * mProgress; mProgressDrawable.setSweepAngle(sweepAngle); mProgressDrawable.draw(canvas); } public boolean isIndeterminateProgressMode() { return mIndeterminateProgressMode; } public void setIndeterminateProgressMode(boolean indeterminateProgressMode) { this.mIndeterminateProgressMode = indeterminateProgressMode; } @Override protected boolean verifyDrawable(Drawable who) { return who == mAnimatedDrawable || super.verifyDrawable(who); } private MorphingAnimation createMorphing() { mMorphingInProgress = true; MorphingAnimation animation = new MorphingAnimation(this, background); animation.setFromCornerRadius(mCornerRadius); animation.setToCornerRadius(mCornerRadius); animation.setFromWidth(getWidth()); animation.setToWidth(getWidth()); if (mConfigurationChanged) { animation.setDuration(MorphingAnimation.DURATION_INSTANT); } else { animation.setDuration(MorphingAnimation.DURATION_NORMAL); } mConfigurationChanged = false; return animation; } private MorphingAnimation createProgressMorphing(float fromCorner, float toCorner, int fromWidth, int toWidth) { mMorphingInProgress = true; MorphingAnimation animation = new MorphingAnimation(this, background); animation.setFromCornerRadius(fromCorner); animation.setToCornerRadius(toCorner); animation.setPadding(mPaddingProgress); animation.setFromWidth(fromWidth); animation.setToWidth(toWidth); if (mConfigurationChanged) { animation.setDuration(MorphingAnimation.DURATION_INSTANT); } else { animation.setDuration(MorphingAnimation.DURATION_NORMAL); } mConfigurationChanged = false; return animation; } //切换到进度条形式 private void morphToProgress() { setWidth(getWidth()); setText(mProgressText); MorphingAnimation animation = createProgressMorphing(mCornerRadius, getHeight(), getWidth(), getHeight()); animation.setFromColor(getNormalColor(mIdleColorState)); animation.setToColor(mColorProgress); animation.setFromStrokeColor(getNormalColor(mIdleColorState)); animation.setToStrokeColor(mColorIndicatorBackground); animation.setListener(mProgressStateListener); animation.start(); } private OnAnimationEndListener mProgressStateListener = new OnAnimationEndListener() { @Override public void onAnimationEnd() { mMorphingInProgress = false; mState = State.PROGRESS; mStateManager.checkState(CircularProgressButton.this); } }; private void morphProgressToComplete() { MorphingAnimation animation = createProgressMorphing(getHeight(), mCornerRadius, getHeight(), getWidth()); animation.setFromColor(mColorProgress); animation.setToColor(getNormalColor(mCompleteColorState)); animation.setFromStrokeColor(mColorIndicator); animation.setToStrokeColor(getNormalColor(mCompleteColorState)); animation.setListener(mCompleteStateListener); animation.start(); } //idle状态切换为complete状态 private void morphIdleToComplete() { MorphingAnimation animation = createMorphing(); animation.setFromColor(getNormalColor(mIdleColorState)); animation.setToColor(getNormalColor(mCompleteColorState)); animation.setFromStrokeColor(getNormalColor(mIdleColorState)); animation.setToStrokeColor(getNormalColor(mCompleteColorState)); animation.setListener(mCompleteStateListener); animation.start(); } private OnAnimationEndListener mCompleteStateListener = new OnAnimationEndListener() { @Override public void onAnimationEnd() { if (mIconComplete != 0) { setText(null); setIcon(mIconComplete); } else { setText(mCompleteText); } mMorphingInProgress = false; mState = State.COMPLETE; mStateManager.checkState(CircularProgressButton.this); } }; private void morphCompleteToIdle() { MorphingAnimation animation = createMorphing(); animation.setFromColor(getNormalColor(mCompleteColorState)); animation.setToColor(getNormalColor(mIdleColorState)); animation.setFromStrokeColor(getNormalColor(mCompleteColorState)); animation.setToStrokeColor(getNormalColor(mIdleColorState)); animation.setListener(mIdleStateListener); animation.start(); } //error状态切换到idle状态 private void morphErrorToIdle() { MorphingAnimation animation = createMorphing(); animation.setFromColor(getNormalColor(mErrorColorState)); animation.setToColor(getNormalColor(mIdleColorState)); animation.setFromStrokeColor(getNormalColor(mErrorColorState)); animation.setToStrokeColor(getNormalColor(mIdleColorState)); animation.setListener(mIdleStateListener); animation.start(); } private OnAnimationEndListener mIdleStateListener = new OnAnimationEndListener() { @Override public void onAnimationEnd() { removeIcon(); setText(mIdleText); mMorphingInProgress = false; mState = State.IDLE; mStateManager.checkState(CircularProgressButton.this); } }; //idle状态切换到error状态 private void morphIdleToError() { MorphingAnimation animation = createMorphing(); animation.setFromColor(getNormalColor(mIdleColorState)); animation.setToColor(getNormalColor(mErrorColorState)); animation.setFromStrokeColor(getNormalColor(mIdleColorState)); animation.setToStrokeColor(getNormalColor(mErrorColorState)); animation.setListener(mErrorStateListener); animation.start(); } //进度条切换到error状态 private void morphProgressToError() { MorphingAnimation animation = createProgressMorphing(getHeight(), mCornerRadius, getHeight(), getWidth()); animation.setFromColor(mColorProgress); animation.setToColor(getNormalColor(mErrorColorState)); animation.setFromStrokeColor(mColorIndicator); animation.setToStrokeColor(getNormalColor(mErrorColorState)); animation.setListener(mErrorStateListener); animation.start(); } private OnAnimationEndListener mErrorStateListener = new OnAnimationEndListener() { @Override public void onAnimationEnd() { if (mIconError != 0) { setText(null); setIcon(mIconError); } else { setText(mErrorText); } mMorphingInProgress = false; mState = State.ERROR; mStateManager.checkState(CircularProgressButton.this); } }; private void morphProgressToIdle() { MorphingAnimation animation = createProgressMorphing(getHeight(), mCornerRadius, getHeight(), getWidth()); animation.setFromColor(mColorProgress); animation.setToColor(getNormalColor(mIdleColorState)); animation.setFromStrokeColor(mColorIndicator); animation.setToStrokeColor(getNormalColor(mIdleColorState)); animation.setListener(new OnAnimationEndListener() { @Override public void onAnimationEnd() { removeIcon(); setText(mIdleText); mMorphingInProgress = false; mState = State.IDLE; mStateManager.checkState(CircularProgressButton.this); } }); animation.start(); } private void setIcon(int icon) { Drawable drawable = getResources().getDrawable(icon); if (drawable != null) { int padding = (getWidth() / 2) - (drawable.getIntrinsicWidth() / 2); setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0); setPadding(padding, 0, 0, 0); } } protected void removeIcon() { setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); setPadding(0, 0, 0, 0); } /** * Set the View's background. Masks the API changes made in Jelly Bean. */ @SuppressWarnings("deprecation") @SuppressLint("NewApi") public void setBackgroundCompat(Drawable drawable) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { setBackground(drawable); } else { setBackgroundDrawable(drawable); } } public void setProgress(int progress) { mProgress = progress; if (mMorphingInProgress || getWidth() == 0) { return; } mStateManager.saveProgress(this); if (mProgress >= mMaxProgress) { if (mState == State.PROGRESS) { morphProgressToComplete(); } else if (mState == State.IDLE) { morphIdleToComplete(); } } else if (mProgress > IDLE_STATE_PROGRESS) { if (mState == State.IDLE) { morphToProgress(); } else if (mState == State.PROGRESS) { invalidate(); } } else if (mProgress == ERROR_STATE_PROGRESS) { if (mState == State.PROGRESS) { morphProgressToError(); } else if (mState == State.IDLE) { morphIdleToError(); } } else if (mProgress == IDLE_STATE_PROGRESS) { if (mState == State.COMPLETE) { morphCompleteToIdle(); } else if (mState == State.PROGRESS) { morphProgressToIdle(); } else if (mState == State.ERROR) { morphErrorToIdle(); } } } public int getProgress() { return mProgress; } public void setBackgroundColor(int color) { background.getGradientDrawable().setColor(color); } public void setStrokeColor(int color) { background.setStrokeColor(color); } public String getIdleText() { return mIdleText; } public String getCompleteText() { return mCompleteText; } public String getErrorText() { return mErrorText; } public void setIdleText(String text) { mIdleText = text; } public void setCompleteText(String text) { mCompleteText = text; } public void setErrorText(String text) { mErrorText = text; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { setProgress(mProgress); } } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState savedState = new SavedState(superState); savedState.mProgress = mProgress; savedState.mIndeterminateProgressMode = mIndeterminateProgressMode; savedState.mConfigurationChanged = true; return savedState; } @Override public void onRestoreInstanceState(Parcelable state) { if (state instanceof SavedState) { SavedState savedState = (SavedState) state; mProgress = savedState.mProgress; mIndeterminateProgressMode = savedState.mIndeterminateProgressMode; mConfigurationChanged = savedState.mConfigurationChanged; super.onRestoreInstanceState(savedState.getSuperState()); setProgress(mProgress); } else { super.onRestoreInstanceState(state); } } static class SavedState extends BaseSavedState { private boolean mIndeterminateProgressMode; private boolean mConfigurationChanged; private int mProgress; public SavedState(Parcelable parcel) { super(parcel); } private SavedState(Parcel in) { super(in); mProgress = in.readInt(); mIndeterminateProgressMode = in.readInt() == 1; mConfigurationChanged = in.readInt() == 1; } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(mProgress); out.writeInt(mIndeterminateProgressMode ? 1 : 0); out.writeInt(mConfigurationChanged ? 1 : 0); } public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { @Override public SavedState createFromParcel(Parcel in) { return new SavedState(in); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; } }
上面就是源码,个别的细节知识点就不做讲解,只说说总体的思路,通过重写drawableStateChanged()方法监听Button的State状态改变,然后针对状态做我们指定的状态切换,这里就涉及到各个initXXXStateDrawable()方法,通过这些方法实现各个State状态对应的StateListDrawable对应及其他相关变量的初始化,然后在onLayout方法中通过setProgress()方法实现切换,这个方法中根据不同的progress判断,进行状态的切换,主要是执行一系列的morphToXX方法,在这个方法里,封装了一个MorphingAnimation对象,该对象内部通过封装的属性动画实现控件宽度长度的渐变,达到改变控件大小的效果。最后在更新控件,实现效果。
改变主要针对整体的思路,具体的实现细节还需进一步研究源码,特此记录下Drawable的不错案例。