码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-05-31 00:08:18      阅读:370      评论:0      收藏:0      [点我收藏+]

标签:

转载请注明出处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(),只能改变圆环弧度,而不引起状态的改变(也就是不引起动画)。

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

标签:

原文地址:http://blog.csdn.net/crazy__chen/article/details/46278423

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!