标签:
转载请注明出处http://blog.csdn.net/crazy__chen/article/details/46278423
源码下载http://download.csdn.net/detail/kangaroo835127729/8755815
dmytrodanylyk/circular-progress-button是github上一个开源的按钮控件,这个是链接https://github.com/dmytrodanylyk/circular-progress-button
下面是示例图,应该说作为按钮,设计非常的简洁大方,这篇文章就是来介绍一下这个circular-progress-button的源码,让大家明白这么漂亮的控件,是怎么写出来的。关键是思路。
对于控件的简单使用,大家可以看http://www.eoeandroid.com/forum.php?mod=viewthread&tid=535726,我在这里就不必多介绍了,因为这个控件主要涉及一些动画和自定义drawable的知识,有些类或者方法大家没有见过,一定要看api手册,当然我也会做简单的说明。
下面先来介绍一下整个控件书写的大体思路:
首先作为一个按钮,我们自然要继承Button类。另外circular-progress-button有一些自定义的的属性,例如按钮提示字符串,按钮圆角,按钮提示icon,加载圆环的颜色等,这些都是需要我们在attrs文件里面定义然后在控件初始化的时候读取的,这是制作控件的一般步骤。
接着具体就这个控件而言,我们可以看到,有几种状态,一个是初始状态(蓝色),样式为普通按钮,还有是完成(绿色),失败(红色)状态,上述的几个状态,都是button的自带样式就可以解决的(除了圆角)。另外我们还注意到,在加载过程中,有个圆环状态,这里我们就需要重新写ondraw()方法,来制作自己的样式。
对于每种状态,我们在对按钮做不同的操作时,也就是不同的事件(pressed,focused,enabled,disabled,也可以说是状态,但是为了跟我们自定义的状态区分,我说成事件),可以发现按钮颜色有变化,这个我们通常通过定义自己的xml文件selector来定义不同状态下颜色,然后将这个xml设为控件背景就可以了,但是这里的状况有所不同,就是按钮的每个状态,对事件都有自己的一套颜色,这就要求我们在java文件里面,动态地定义这些属性,于是要使用到StateListDrawable,ColorStateList。
所有状态都可以切换,一个比较典型的过程就是,初始状态-》加载-》完成。从gif图中我们可以看到,这个有动画效果(从按钮,缩成圆环,而且带有进度,反之则从圆环到按钮),所有这样我们必定要定义animation。另外一个典型过程是初始状态-》完成,这个过程中,与上述过程相比,没有圆角的变化。
其他状态的相互转化,都是上述两个过程的相似过程,或者反过程,在接下了的源码中大家就可以看到。
OK,我们有了大体的思路,弄懂了那些方面的工作是我们要做的,接下来通过源码,看看要怎么做。
首先来一些基本属性,这些属性供接下来的大家看源码的时候参考,不必每个都细致地看,不知道的时候在回来找
public class CircularProgressButton extends Button { /** * 状态代号 * 0为初始状态,-1失败状态,100为完成状态,50为不明确中间状态 */ 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; /** * 背景StrokeGradientDrawable * A Drawable with a color gradient for buttons, backgrounds, etc. * It can be defined in an XML file with the <shape> element. */ private StrokeGradientDrawable background; /** * 环形动画背景 */ private CircularAnimatedDrawable mAnimatedDrawable; /** * 环形进度背景 */ private CircularProgressDrawable mProgressDrawable; /** * ColorStateList对象可以在XML中定义,像color一样使用,它能根据它应用到的View对象的状态实时改变颜色 * 当每次状态改变时,StateList都会从上到下遍历一次,第一个匹配当前状态的item将被使用——选择的过程不是基于“最佳匹配”, * 只是符合state的最低标准的第一个item。 */ private ColorStateList mIdleColorState; /** * 完成状态ColorStateList */ private ColorStateList mCompleteColorState; /** * 失败状态ColorStateList */ private ColorStateList mErrorColorState; /** * 用于根据状态改变drawable * 初始状态背景 */ private StateListDrawable mIdleStateDrawable; /** * 完成状态背景 */ private StateListDrawable mCompleteStateDrawable; /** * 失败状态背景 */ private StateListDrawable mErrorStateDrawable; /** * 状态管理器 */ private StateManager mStateManager; /** * 当前状态 */ private State mState; /** * 初始提示文字 */ private String mIdleText; /** * 完成提示文字 */ private String mCompleteText; /** * 失败提示文字 */ private String mErrorText; /** * 中间过程提示文字 */ private String mProgressText; /** * 中间圆形的颜色 */ private int mColorProgress; /** * 加载进度的颜色 */ private int mColorIndicator; /** * 加载进度的背景色 */ private int mColorIndicatorBackground; /** * 成功时的图标 */ private int mIconComplete; /** * 失败时的图标 */ private int mIconError; /** * 笔触大小 */ private int mStrokeWidth; /** * 圆环与按钮之间的padding */ private int mPaddingProgress; /** * 按钮圆角 */ private float mCornerRadius; /** * 是否为不明确状态,也就是加载过程中,没有指定process的具体值 */ private boolean mIndeterminateProgressMode; /** * 设备状态是否变化 */ private boolean mConfigurationChanged; /** * 当前状态 enum */ private enum State { PROGRESS, IDLE, COMPLETE, ERROR } /** * 最大进度 */ private int mMaxProgress; /** * 当前进度 */ private int mProgress; /** * 是否在加载过程 */ private boolean mMorphingInProgress;
接下来我们看构造函数和初始化方法
public CircularProgressButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs); } /** * 初始化 * @param context * @param attributeSet */ 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);//设置初始状态提示文字 initIdleStateDrawable();//创建初始状态drawable对象 setBackgroundCompat(mIdleStateDrawable);//设置背景 }
我们逐个看他们具体是怎么做的。
我首先来看加载自动属性的initAttributes(context, attributeSet)方法
/** * 初始化attr属性 * @param context * @param attributeSet */ 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); //默认颜色rgb int blue = getColor(R.color.cpb_blue); int white = getColor(R.color.cpb_white); int grey = getColor(R.color.cpb_grey); //idle状态(初始状态)Selector_id int idleStateSelector = attr.getResourceId(R.styleable.CircularProgressButton_cpb_selectorIdle, R.color.cpb_idle_state_selector); //根据Selector_id获取ColorStateList对象 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(); } }一些基本的属性获取不多做解释,重要的是attr.getResourceId()这个方法,获取了我们自定义的selector文件的id,然后getResources().getColorStateList(idleStateSelector)根据id获取了ColorStateList对象。这个对象的主要作用就是,可以根据不同值(事件),来获取selector文件的定义的颜色。这就是我们在思路部分提到的,动态地为每个状态设置selector,解决思路就是,为每个状态定义一个selector文件(其实是三个,初始状态,完成状态,失败状态,而加载状态是圆环,所以不必定义,这三个文件我没有贴出来,大家可以在我提供的源码中找到),然后为每个selector创建一个ColorStateList对象,然后为每个StateListDrawable对象(其实就是背景对象,不难想象,三个状态都分别对应着一个StateListDrawable对象,每个drawable对象对应一个事件,在下面的源码会看到)设置对应的ColorStateList就可以了。这里我首先记得我们获得了ColorStateList对象先,接下会使用到它们。
接下来是
mStateManager = new StateManager(this);我们创建了一个用于管理状态的对象,这个对象主要功能是变更CircularProgressButton的状态,比较简单,我直接贴出代码
/** * 状态管理类 * @author Administrator * */ class StateManager { /** * 是否enbale */ private boolean mIsEnabled; /** * 当前进度值 */ private int mProgress; public StateManager(CircularProgressButton progressButton) { mIsEnabled = progressButton.isEnabled(); mProgress = progressButton.getProgress(); } public void saveProgress(CircularProgressButton progressButton) { mProgress = progressButton.getProgress(); } public boolean isEnabled() { return mIsEnabled; } public int getProgress() { return mProgress; } /** * 检查button当前状态与保留的状态是否相同 * 不同,则更新状态 * 相同,检查是否enable * @param progressButton */ public void checkState(CircularProgressButton progressButton) { if (progressButton.getProgress() != getProgress()) { progressButton.setProgress(progressButton.getProgress()); } else if(progressButton.isEnabled() != isEnabled()) { progressButton.setEnabled(progressButton.isEnabled()); } } }这个类主要的方法就是checkState(),用于检测当前CircularProgressButton的进度,与上一次的进度是否相同,不同则更新。
接下来是initIdleStateDrawable()方法,看这个方法名字我们就知道,是要创建初始状态的StateListDrawable(背景)对象
/** * 初始化初始状态StateDrawable */ private void initIdleStateDrawable() { //利用ColorState获得不同状态下的color 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(); //添加不同事件对应的drawable mIdleStateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed.getGradientDrawable()); mIdleStateDrawable.addState(new int[]{android.R.attr.state_focused}, drawableFocused.getGradientDrawable()); //enable去负,就是disable mIdleStateDrawable.addState(new int[]{-android.R.attr.state_enabled}, drawableDisabled.getGradientDrawable()); mIdleStateDrawable.addState(StateSet.WILD_CARD, background.getGradientDrawable()); }
那么我们这么创建drawable对象,又怎么把事件和drawable对象对应起来,再加入StateListDrawable呢?
创建drawable,首先利用ColorStateList获得不同事件下的颜色
//利用ColorState获得不同状态下的color int colorNormal = getNormalColor(mIdleColorState); int colorPressed = getPressedColor(mIdleColorState); int colorFocused = getFocusedColor(mIdleColorState); int colorDisabled = getDisabledColor(mIdleColorState);
/** * 获取enabled状态是的color * @param colorStateList * @return */ private int getNormalColor(ColorStateList colorStateList) { return colorStateList.getColorForState(new int[]{android.R.attr.state_enabled}, 0); } /** * 获取pressed状态是的color * @param colorStateList * @return */ private int getPressedColor(ColorStateList colorStateList) { return colorStateList.getColorForState(new int[]{android.R.attr.state_pressed}, 0); } /** * 获取focused状态是的color * @param colorStateList * @return */ private int getFocusedColor(ColorStateList colorStateList) { return colorStateList.getColorForState(new int[]{android.R.attr.state_focused}, 0); } /** * 获取disabled状态是的color * @param colorStateList * @return */ private int getDisabledColor(ColorStateList colorStateList) { return colorStateList.getColorForState(new int[]{-android.R.attr.state_enabled}, 0); }
然后逐一创建,使用createDrawable()方法
StrokeGradientDrawable drawableDisabled = createDrawable(colorDisabled); StrokeGradientDrawable drawableFocused = createDrawable(colorFocused); StrokeGradientDrawable drawablePressed = createDrawable(colorPressed);
/** * 创建背景StrokeGradientDrawable * @param color 状态颜色 * @return */ 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; }创建了一个GradientDrawable,设置了圆角,颜色等属性,然后利用自定义的StrokeGradientDrawable对象,设置了笔触大小,笔触颜色(这就是为什么要自定义一个StrokeGradientDrawable类的原因,GradientDrawable没有这两个属性)
public class StrokeGradientDrawable { /** * 笔触宽度 */ private int mStrokeWidth; /** * 笔触颜色 */ private int mStrokeColor; private GradientDrawable mGradientDrawable; public StrokeGradientDrawable(GradientDrawable drawable) { mGradientDrawable = drawable; } public int getStrokeWidth() { return mStrokeWidth; } public void setStrokeWidth(int strokeWidth) { mStrokeWidth = strokeWidth; mGradientDrawable.setStroke(strokeWidth, getStrokeColor()); } public int getStrokeColor() { return mStrokeColor; } public void setStrokeColor(int strokeColor) { mStrokeColor = strokeColor; mGradientDrawable.setStroke(getStrokeWidth(), strokeColor); } public GradientDrawable getGradientDrawable() { return mGradientDrawable; } }最后,将事件对应的drawbale加入StateListDrawable
mIdleStateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed.getGradientDrawable()); mIdleStateDrawable.addState(new int[]{android.R.attr.state_focused}, drawableFocused.getGradientDrawable()); //enable去负,就是disable mIdleStateDrawable.addState(new int[]{-android.R.attr.state_enabled}, drawableDisabled.getGradientDrawable()); mIdleStateDrawable.addState(StateSet.WILD_CARD, background.getGradientDrawable());
/** * 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); } }
到目前为止,我们只是完成了一些初始化的过程,为控件设置了背景(这个背景按被press,focus会变化),也就是完成了第一步,初始状态的绘制(当然我们没有看ondraw函数,不过我们可以想象,在调用这个函数的时候,mIdleStateDrawable会被绘制到button上)。
接下来,我们看一下其他的状态,首先要说明这个函数
@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(); } }drawableStateChanged()函数继承自TextView(Button继承自TextView),当控件状态变化的时候,会自动调用。我们可以看到根据不同的状态,会有不同的init..函数,来做背景的初始化工作,然后设置背景,这些过程都跟上述的mIdleStateDrawable大同小异,我就不再赘述。
那么,状态到底什么时候会变化呢?状态本身是不会主动变化的,我们需要主动调用CircularProgressButton的setProgress()方法,来变换它的状态。
所以我们从这个方法看起
/** * 设置进度 * @param progress */ public void setProgress(int progress) { mProgress = progress; if (mMorphingInProgress || getWidth() == 0) {//如果view不变化或者宽度为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) {//如果当前进度为-1 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();//运行从失败到初始动画 } } }
-1表示失败状态,0表示初始状态,100表示成功,0-100之间,则表示加载状态。上面的条件语句,只是对不同状态间的切换,调用了不同的动画。
我们取morphProgressToComplete()来看
/** * 开始从加载状态到完成状态的动画 */ 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(); }
** * 创建渐变动画(涉及加载过程,宽度,圆角变化) * @param fromCorner * @param toCorner * @param fromWidth * @param toWidth * @return */ 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; }
/** * 渐变动画类 * @author Administrator * */ class MorphingAnimation { /** * 默认动画时间,400毫秒 */ public static final int DURATION_NORMAL = 400; /** * 默认动画时间,瞬间 */ public static final int DURATION_INSTANT = 1; //监听器 private OnAnimationEndListener mListener; /** * 动画时间 */ private int mDuration; //起止宽度 private int mFromWidth; private int mToWidth; //起止颜色 private int mFromColor; private int mToColor; //起止笔触颜色 private int mFromStrokeColor; private int mToStrokeColor; //起止圆角 private float mFromCornerRadius; private float mToCornerRadius; //内边距 private float mPadding; //文字 private TextView mView; //背景 private StrokeGradientDrawable mDrawable; public MorphingAnimation(TextView viewGroup, StrokeGradientDrawable drawable) { mView = viewGroup; mDrawable = drawable; } public void setDuration(int duration) { mDuration = duration; } public void setListener(OnAnimationEndListener listener) { mListener = listener; } public void setFromWidth(int fromWidth) { mFromWidth = fromWidth; } public void setToWidth(int toWidth) { mToWidth = toWidth; } public void setFromColor(int fromColor) { mFromColor = fromColor; } public void setToColor(int toColor) { mToColor = toColor; } public void setFromStrokeColor(int fromStrokeColor) { mFromStrokeColor = fromStrokeColor; } public void setToStrokeColor(int toStrokeColor) { mToStrokeColor = toStrokeColor; } public void setFromCornerRadius(float fromCornerRadius) { mFromCornerRadius = fromCornerRadius; } public void setToCornerRadius(float toCornerRadius) { mToCornerRadius = toCornerRadius; } public void setPadding(float padding) { mPadding = padding; } public void start() { //属性值设置动画 ValueAnimator widthAnimation = ValueAnimator.ofInt(mFromWidth, mToWidth); final GradientDrawable gradientDrawable = mDrawable.getGradientDrawable(); widthAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Integer value = (Integer) animation.getAnimatedValue(); int leftOffset;//左偏移 int rightOffset;//右偏移 int padding;//内边距 if (mFromWidth > mToWidth) {//如果起始宽度大于终止宽度 leftOffset = (mFromWidth - value) / 2; rightOffset = mFromWidth - leftOffset; padding = (int) (mPadding * animation.getAnimatedFraction()); } else { leftOffset = (mToWidth - value) / 2; rightOffset = mToWidth - leftOffset; padding = (int) (mPadding - mPadding * animation.getAnimatedFraction()); } //设置背景范围 gradientDrawable .setBounds(leftOffset + padding, padding, rightOffset - padding, mView.getHeight() - padding); } }); //背景 ObjectAnimator bgColorAnimation = ObjectAnimator.ofInt(gradientDrawable, "color", mFromColor, mToColor); bgColorAnimation.setEvaluator(new ArgbEvaluator()); //描边动画 ObjectAnimator strokeColorAnimation = ObjectAnimator.ofInt(mDrawable, "strokeColor", mFromStrokeColor, mToStrokeColor); strokeColorAnimation.setEvaluator(new ArgbEvaluator()); //圆角动画 ObjectAnimator cornerAnimation = ObjectAnimator.ofFloat(gradientDrawable, "cornerRadius", mFromCornerRadius, mToCornerRadius); //动画集 AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setDuration(mDuration); animatorSet.playTogether(widthAnimation, bgColorAnimation, strokeColorAnimation, cornerAnimation); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (mListener != null) { mListener.onAnimationEnd(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animatorSet.start(); } }
首先之所以要定义MorphingAnimation这个类,是因为按钮的变化,不止是一个属性值在变化,而是多个(圆角,背景颜色,宽度,描边等)。
ValueAnimator widthAnimation = ValueAnimator.ofInt(mFromWidth, mToWidth)
用于控制宽度的变化,其实ValueAnimator本身不产生动画效果,只是根据起始值和终止值,提供当前值(类似Scroller的用法,关于Scroller,大家可以参考本专栏)。
真正改变控件大小的,是在onAnimationUpdate()函数中,gradientDrawable.setBounds()方法,根据当前值,计算当前控件宽高范围
然后是ObjectAnimator bgColorAnimation = ObjectAnimator.ofInt(gradientDrawable, "color", mFromColor, mToColor);
用于颜色变化,ObjectAnimator是android用于提供属性动画的类
接着是描边动画,圆角动画,都是使用了ObjectAnimator,最后AnimatorSet来同时播放这些动画,从而产生复制的动画效果
要理解上面代码,需要大家对android的动画比较熟悉,大家可以通过api来了解,对于基础,我不在这里过多说明。
接下去,我们再看一个动画morphIdleToComplete()
/** * 开始从初始状态到完成状态的动画 */ 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(); }这个动画貌似跟前一个没有什么区别,就是调用了另外一个创建动画的方法createMorphing()
/** * 创建渐变动画(不涉及加载过程,故宽度,圆角不必变化) * @param fromCorner * @param toCorner * @param fromWidth * @param toWidth * @return */ 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; }
至于其他状态间的动画,其实也是调用了createMorphing()和createProgressMorphing()来创建,只是传入的起始值和终止值不同(例如从起始状态到加载状态的动画,和从加载状态到起始状态的动画,所以属性反过来设置就可以了)
到这里为止,我们讲完了状态间切换动画的创建和使用过程,可是令人疑惑的是,我们还没有提及ondraw()方法。根据我一开始的说明,我们必须覆写ondraw()方法,才能画出圆环。由于圆环(加载状态)比较特殊,我将在下一篇文章说明,对于加载状态,我们利用setProcess(),只能改变圆环弧度,而不引起状态的改变(也就是不引起动画)。
dmytrodanylyk/circular-progress-button源码解析(一)
标签:
原文地址:http://blog.csdn.net/crazy__chen/article/details/46278423