dmytrodanylyk/circular-progress-button源码解析(一)

来源:互联网 发布:ch341a编程器软件1.30 编辑:程序博客网 时间:2024/04/29 01:49

转载请注明出处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;

上面的属性,我加了注释,可能中文表达不是特别好,大家接下去看就可以知道每个属性的意义。比较重要的,有一些StateListDrawable对象,表示每个状态的背景,还有一些static final的属性,用于标记当前状态(例如-1表示失败,0表示初始,100表示完成等),还有mProgress表示进度值,其他的话就是一些颜色,提示文字,内边距之类的属性。

接下来我们看构造函数和初始化方法

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);//设置背景    }

初始化方法首先获得了笔触大小,这个笔触用于绘制圆环(决定圆环的厚度),另外加载了自定义属性,设置当前状态为State.IDLE(初始状态),初始化状态管理器(自定义的一个类),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());    }

正如上面说的,我为初始状态创建StateListDrawable,为什么是StateListDrawable(可以理解为一系列drawable对象),而不是简单drawable呢?因为刚才说的,每个事件(pressed,focused,enabled,disabled)对应一个drawable对象,每个状态对应一个StateListDrawable对象。

那么我们这么创建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());

OK,经过上述过程,我们完成了mIdleStateDrawable(初始背景对象)的创建,然后我们为控件,设置这个背景,调用了setBackgroundCompat()方法

 /**     * 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();//运行从失败到初始动画            }        }    }

上面的注释非常清楚,就是比较setProcess()以后的状态,和之前的状态,根据这两个状态,调用不同的动画

-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();    }

创建动画,做了一些设置,然后start,关键是createProgressMorphing()方法

**     * 创建渐变动画(涉及加载过程,宽度,圆角变化)     * @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;    }

其实也是创建了一个MorphingAnimation对象,设置了一些属性,关键是MorphingAnimation类

/** * 渐变动画类 * @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();    }}

这个类也不特别,设置了一些属性,关键是来看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()区别就在于,是否有圆角的变化动画,因为createProgressMorphing()是有经过加载状态(也就是要从矩形,编程圆形),所以有圆角的变化。

至于其他状态间的动画,其实也是调用了createMorphing()和createProgressMorphing()来创建,只是传入的起始值和终止值不同(例如从起始状态到加载状态的动画,和从加载状态到起始状态的动画,所以属性反过来设置就可以了)


到这里为止,我们讲完了状态间切换动画的创建和使用过程,可是令人疑惑的是,我们还没有提及ondraw()方法。根据我一开始的说明,我们必须覆写ondraw()方法,才能画出圆环。由于圆环(加载状态)比较特殊,我将在下一篇文章说明,对于加载状态,我们利用setProcess(),只能改变圆环弧度,而不引起状态的改变(也就是不引起动画)。

0 0
原创粉丝点击