先看效果:(最上面那个颜色录像有点问题了)
看见支付宝的支付结果 那个效果还不错,想到那实现一个类似的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:
2.path,pathMeasure:
3.paint:
4.事件监听:
5.要数据持久处理
6.动画即时结束
三.思路
四:源码
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好好相处。。。哈)