使用PathMeasure实现 动画CheckBox

来源:互联网 发布:红蜘蛛软件 路由器 编辑:程序博客网 时间:2024/06/05 03:05

先看效果:(最上面那个颜色录像有点问题了)


看见支付宝的支付结果 那个效果还不错,想到那实现一个类似的checkBox吧。

这个view有点击事件,选中监听,快速点击动画流畅,支持wrapcontent,数据持久。

主角是PathMeasure中的getSegment(...):

public boolean getSegment (float startD, float stopD, Path dst, boolean startWithMoveTo)
Given a start and stop distance, return in dst the intervening segment(s). If the segment is zero-length, return false, else return true. startD and stopD are pinned to legal values (0..getLength()). If startD <= stopD then return false (and leave dst untouched). Begin the segment with a moveTo if startWithMoveTo is true.
On KITKAT and earlier releases, the resulting path may not display on a hardware-accelerated Canvas. A simple workaround is to add a single operation to this path, such as dst.rLineTo(0, 0).

简单理解就是,从原有的path中截取出来其中一段path。

一.使用到的知识点:

1.属性动画,AnimatorSet

2.path,pathMeasure,pathMeasure.getSegment(...);

3.paint ,shader

二.聊聊知识点中注意的地方:

1.属性动画,AnimatorSet:

1).快速点击,防止动画会从0开始,那需要将上次动画结束点作为下次动画的起始点:ObjectAnimator.ofFloat(mPercentCheck, 1);
(这里补充下动画的cancel和end知识点,举个栗子就明白了:
属性动画:value从0到1。
在动画执行一半时,调用anim.cancel():value定格在cancel时(value:0.5),然后调用监听中的onCancel(),然后onEnd()。
在动画执行一半时,调用anim.end():value直接跳到最终值(value:1),然后onEnd().
)
2).要实现圆和对号动画分步进行,AnimatorSet使用顺序播放动画:playSequentially(vaCircleCheck, vaCheckCheck);

2.path,pathMeasure:

1).pathMeasure.getLength()的注释:Return the total length of the current contour。 Current contour !
measureCheck.nextContour()的注释:Move to the next contour in the path。
原来pathMeasure长度还分段的。。意思就是:如果path画了一个圆,然后接着再画一条直线。那么pathMeasure.getLenght()获取到的长度只为圆的长度。想要得到直线的长度,必须先调用nextContour(),再次getLength()才为直线的长度。测试了好久,这个contour到底怎么区分的还是很晕。。
2).pathMeasure.getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo):从原path中截取出starD至stopD的dst。
startD:起始点
stopD:结束点
dst:截取到的path
startWithMoveTo:true:再次截取,起始点为0时,还是原path的起始点。
false:再次截取,起始点为0时,为上次截取的终点。
3)path.addCircle (float x, float y, float radius, Path.Direction dir):
 要做好padding的处理。需要将padding主动加上圆环的宽度的一半,否则会出界面。
最后一个参数很关键,如果想要让圆换一个方向转,那设置为CW即可(顺时针)。CWW(逆时针)。

3.paint:

1).cap设置为round,线的顶端是圆形。join设置为round,线的拐弯处是圆弧。

4.事件监听:

1).自定义view 中重写了一些监听事件,那么需要再定义一个监听事件,供用户重写。(此view点击事件需要使用OnClickListenerEx)

5.要数据持久处理

1)重写onSaveInstanceState()和onRestoreInstanceState()。

6.动画即时结束

1)onDetachedFromWindow()方法中,将动画cancel()掉。

三.思路

1.画圆环和画对号。
2.通过pathMeasure.getLenght()获取到圆环和对号的路径长度。
3.获取属性动画的值,获取到当前路径长度。
4.用getSegment(...)得到新的path。
5.画出新的path。

四:源码

package com.dup.bitmapdemo.view;import android.animation.AnimatorSet;import android.animation.ObjectAnimator;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PathMeasure;import android.graphics.SweepGradient;import android.os.Bundle;import android.os.Parcelable;import android.util.AttributeSet;import android.view.View;import android.view.animation.AccelerateDecelerateInterpolator;/** * Created by dup on 16-8-2. */public class PathCheckBoxView extends View implements View.OnClickListener {    private static final String INSTANCE_STATUE = "status";    private static final String INSTANCE_CHECK = "check";    private static final String INSTANCE_ENABLE = "enable";    private Context context;    private static int duration = 300;    //wrapcontent时默认大小:dp    private int defaultSize = 40;    private int viewSize = defaultSize;    //背景灰色线条paint    private Paint mBackPathPaint;    //前线条paint    private Paint mDstPaint;    //背景 圆    private Path mCirclePath;    //前景 圆    private Path mCircleDst;    //背景 对号    private Path mCheckPath;    //前景 对号    private Path mCheckDst;    //圆圈 measure    private PathMeasure measureCircle;    //对号 measure    private PathMeasure measureCheck;    //圆圈长度    private float lengthCircle;    //对号长度    private float lengthCheck;    //圆圈动画进度    private float mPercentCircle = 0;    //对号进度    private float mPercentCheck = 0;    //选中动画    private AnimatorSet asCheck;    //取消选中动画    private AnimatorSet asUnCheck;    //选中动画---圆环动画    private ValueAnimator vaCheckCheck;    // ---对号动画    private ValueAnimator vaCircleCheck;    private ValueAnimator vaCircleUnCheck;    private ValueAnimator vaCheckUnCheck;    private boolean isEnabled = true;    public boolean isEnabled() {        return isEnabled;    }    public void setEnabled(boolean isEnabled) {        this.isEnabled = isEnabled;    }    /**     * 标志位     */    private boolean isChecked = false;    public boolean isChecked() {        return isChecked;    }    public void setChecked(boolean is) {        if (!isEnabled) {            return;        }        isChecked = is;        if (mCheckChangedListener != null) {            mCheckChangedListener.onCheckedChanged(this, isChecked);        }        if (isChecked) {            startCheckAnim();        } else {            startUnCheckAnim();        }    }    public void toggle() {        if(isEnabled()){            isChecked = !isChecked;            setChecked(isChecked);        }    }    public PathCheckBoxView(Context context) {        this(context, null);    }    public PathCheckBoxView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public PathCheckBoxView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.context = context;        this.setOnClickListener(this);        setClickable(true);    }    @Override    protected Parcelable onSaveInstanceState() {        Bundle bundle = new Bundle();        bundle.putParcelable(INSTANCE_STATUE, super.onSaveInstanceState());        bundle.putBoolean(INSTANCE_CHECK, isChecked);        bundle.putBoolean(INSTANCE_ENABLE, isEnabled);        return bundle;    }    @Override    protected void onRestoreInstanceState(Parcelable state) {        if (state instanceof Bundle) {            Bundle bundle = (Bundle) state;            setEnabled(bundle.getBoolean(INSTANCE_ENABLE, true));            setChecked(bundle.getBoolean(INSTANCE_CHECK, false));            super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATUE));        } else {            super.onRestoreInstanceState(state);        }    }    @Override    protected void onDetachedFromWindow() {        super.onDetachedFromWindow();        if(asCheck!=null){            asCheck.cancel();        }        if(asUnCheck!=null){            asUnCheck.cancel();        }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //当为wrapcontent时,将大小设置为默认大小。        if(MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.AT_MOST||MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST){            float scale = context.getResources().getDisplayMetrics().density;            int sizePx = (int) (defaultSize * scale + 0.5f);            setMeasuredDimension(                    MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.AT_MOST?sizePx:MeasureSpec.getSize(widthMeasureSpec),                    MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.AT_MOST?sizePx:MeasureSpec.getSize(heightMeasureSpec)                    );        }else {            super.onMeasure(widthMeasureSpec, heightMeasureSpec);        }    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        if (changed) {            initData(left, top, right, bottom);        }    }    /**     * 初始化具体数据     *     * @param left     * @param top     * @param right     * @param bottom     */    private void initData(int left, int top, int right, int bottom) {        //初始化画笔宽度        int paintWidth = right - left - getPaddingLeft() - getPaddingRight();        int paintHeight = bottom - top - getPaddingTop() - getPaddingBottom();        int paintStroke = Math.min(paintWidth, paintHeight) / 10;//环宽度设为10分之一        //重设padding,加上圆环的宽度,因为不设置,圆环一半会在view外        setPadding(getPaddingLeft() + paintStroke / 2, getPaddingTop() + paintStroke / 2, getPaddingRight() + paintStroke / 2, getPaddingRight() + paintStroke / 2);        //获取到圆环直径        viewSize = Math.min(right - left - getPaddingLeft() - getPaddingRight(), bottom - top - getPaddingTop() - getPaddingBottom());        //初始化背景 线 画笔        mBackPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mBackPathPaint.setColor(Color.LTGRAY);        mBackPathPaint.setStyle(Paint.Style.STROKE);        mBackPathPaint.setStrokeWidth(paintStroke);        mBackPathPaint.setStrokeCap(Paint.Cap.ROUND);        mBackPathPaint.setStrokeJoin(Paint.Join.ROUND);        //初始化前景 线 画笔        mDstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mDstPaint.setStyle(Paint.Style.STROKE);        mDstPaint.setStrokeWidth(paintStroke);        mDstPaint.setColor(Color.BLACK);        mDstPaint.setStrokeCap(Paint.Cap.ROUND);        mDstPaint.setStrokeJoin(Paint.Join.ROUND);        //圆环路径        mCirclePath = new Path();        mCircleDst = new Path();        //对号路径        mCheckPath = new Path();        mCheckDst = new Path();        //pathmeasure        measureCheck = new PathMeasure();        measureCircle = new PathMeasure();        //初始化背景线 路径.这里Direction可以决定动画时圆环的旋转方向        mCirclePath.addCircle(viewSize / 2 + getPaddingLeft(), viewSize / 2 + getPaddingTop(), viewSize / 2, Path.Direction.CCW);        //初始化对号背景线 路径(根据百分比画对号)        float[] floats = new float[2];        floats[0] = getPaddingLeft() + viewSize / 6;        floats[1] = viewSize / 2 + getPaddingTop();        mCheckPath.moveTo(floats[0], floats[1]);        mCheckPath.lineTo(floats[0] + viewSize / 4, floats[1] + viewSize / 4);        mCheckPath.lineTo(floats[0] + 4 * viewSize / 6, floats[1] - viewSize / 7);        //初始化Circle的measure        measureCircle.setPath(mCirclePath, false);        lengthCircle = measureCircle.getLength();        //初始化对号的measure        measureCheck.setPath(mCheckPath, false);        measureCheck.nextContour()        lengthCheck = measureCheck.getLength();        //设置线条渐变        mDstPaint.setShader(                mDstPaint.setShader(new SweepGradient(viewSize / 2 + getPaddingLeft(), viewSize / 2 + getPaddingTop(),                        new int[]{Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE, Color.RED}                        , new float[]{0.1f, 0.3f, 0.5f, 0.7f, 0.9f}                )));    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //画背景圆环        canvas.drawPath(mCirclePath, mBackPathPaint);        //画背景对号        canvas.drawPath(mCheckPath, mBackPathPaint);        mCircleDst.reset();        mCircleDst.lineTo(0, 0);        mCheckDst.reset();        mCheckDst.lineTo(0, 0);        //主角:根据动画进度和pathmeasure测量出的总长度,获取到当前应有长度,从0开始到应有长度.        measureCircle.getSegment(0, lengthCircle * mPercentCircle, mCircleDst, true);        measureCheck.getSegment(0, lengthCheck * mPercentCheck, mCheckDst, true);        canvas.drawPath(mCheckDst, mDstPaint);        canvas.drawPath(mCircleDst, mDstPaint);    }    /**     * 开始未选中动画     */    private void startUnCheckAnim() {        //圆圈不选中动画,这里选用mPercentCircle作为动画起始值,是为了快速勾选时动画不闪        vaCircleUnCheck = ObjectAnimator.ofFloat(mPercentCircle, 0);        vaCircleUnCheck.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mPercentCircle = (float) animation.getAnimatedValue();                invalidate();            }        });        vaCircleUnCheck.setDuration(duration);        //对号不选中动画        vaCheckUnCheck = ObjectAnimator.ofFloat(mPercentCheck, 0);        vaCheckUnCheck.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mPercentCheck = (float) animation.getAnimatedValue();                invalidate();            }        });        vaCheckUnCheck.setDuration(duration);        asUnCheck = new AnimatorSet();        //这里顺序执行anim,保证先执行对号动画。        asUnCheck.playSequentially(vaCheckUnCheck, vaCircleUnCheck);        asUnCheck.setInterpolator(new AccelerateDecelerateInterpolator());        if (asCheck != null && asCheck.isStarted()) {            asCheck.cancel();        }        asUnCheck.start();    }    /**     * 开始选中动画     */    private void startCheckAnim() {        //圆圈选中动画,这里选用mPercentCircle作为动画起始值,是为了快速勾选时动画不闪.        vaCircleCheck = ObjectAnimator.ofFloat(mPercentCircle, 1);        vaCircleCheck.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mPercentCircle = (float) animation.getAnimatedValue();                invalidate();            }        });        vaCircleCheck.setDuration(duration);        //对号选中动画        vaCheckCheck = ObjectAnimator.ofFloat(mPercentCheck, 1);        vaCheckCheck.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mPercentCheck = (float) animation.getAnimatedValue();                invalidate();            }        });        vaCheckCheck.setDuration(duration);        asCheck = new AnimatorSet();        asCheck.setInterpolator(new AccelerateDecelerateInterpolator());        asCheck.playSequentially(vaCircleCheck, vaCheckCheck);        if (asUnCheck != null && asUnCheck.isStarted()) {            asUnCheck.cancel();        }        asCheck.start();    }    @Override    public void onClick(View v) {        if (mClickListener != null) {            mClickListener.onClick(v);        }        toggle();    }    private OnClickListenerEx mClickListener;    /**     * 点击事件监听     *     * @param mListener     */    public void setOnClickListenerEx(OnClickListenerEx mListener) {        this.mClickListener = mListener;    }    public interface OnClickListenerEx {        void onClick(View v);    }    private OnCheckedChangeListener mCheckChangedListener;    public void setOnCheckedChangeListener(OnCheckedChangeListener mListener) {        this.mCheckChangedListener = mListener;    }    public interface OnCheckedChangeListener {        void onCheckedChanged(View view, boolean isChecked);    }}
注意的地方就是使用此view的点击事件需要使用OnClickListenerEx。至于彩虹色,可以修改initData()最后的shader。使用和CheckBox基本没有什么区别(貌似这个view中监听事件不能跟ButterKnife好好相处。。。哈)
主要
0 0