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类
这是一个绘制圆形的自定义Drawable。首先通过CircularProgressDrawable构造方法我们指定圆形的描边宽度和描边颜色,同时初始化圆弧绘制的起始角度为-90和绘制角度0。我们通过setSweepAngle(float sweepAngle)方法设置圆弧绘制的角度,通过createPaint()初始化我们的画笔mPaint对象,通过getRect()方法指定绘制圆弧所在的矩形范围(通过size和描边确定)。最后我们在draw()方法中进行圆弧的绘制。这样,我们就实现了一个圆形进度的Drawable对象的开发。
2、CircularAnimatedDrawable类
从类的声明上来看,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设置不同状态下的样式。同样,这个的实现原理也是通过状态进行判断,针对不同状态之间的切换配以动画的处理,所以显得无比的靓丽。
上面就是源码,个别的细节知识点就不做讲解,只说说总体的思路,通过重写drawableStateChanged()方法监听Button的State状态改变,然后针对状态做我们指定的状态切换,这里就涉及到各个initXXXStateDrawable()方法,通过这些方法实现各个State状态对应的StateListDrawable对应及其他相关变量的初始化,然后在onLayout方法中通过setProgress()方法实现切换,这个方法中根据不同的progress判断,进行状态的切换,主要是执行一系列的morphToXX方法,在这个方法里,封装了一个MorphingAnimation对象,该对象内部通过封装的属性动画实现控件宽度长度的渐变,达到改变控件大小的效果。最后在更新控件,实现效果。
改变主要针对整体的思路,具体的实现细节还需进一步研究源码,特此记录下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的不错案例。