package com.yuyh.library.view.button; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.view.animation.LinearInterpolator; import android.widget.Checkable; import com.yuyh.library.R; import com.yuyh.library.utils.DimenUtils; public class SmoothCheckBox extends View implements Checkable { private static final String KEY_INSTANCE_STATE = "SmoothCheckBox"; private static final int COLOR_TICK = Color.WHITE; private static final int COLOR_UNCHECKED = Color.WHITE; private static final int COLOR_CHECKED = Color.parseColor("#00933B"); private static final int COLOR_FLOOR_UNCHECKED = Color.parseColor("#DFDFDF"); private static final int DEF_DRAW_SIZE = 25; private static final int DEF_ANIM_DURATION = 150; private Paint mPaint, mTickPaint, mFloorPaint; private Point[] mTickPoints; private Point mCenterPoint; private Path mTickPath; private float mLeftLineDistance, mRightLineDistance, mDrewDistance; private float mScaleVal = 1.0f, mFloorScale = 1.0f; private int mWidth, mAnimDuration, mStrokeWidth; private int mCheckedColor, mUnCheckedColor, mFloorColor, mFloorUnCheckedColor; private boolean mChecked; private boolean mTickDrawing; private OnCheckedChangeListener mListener; private int min_width = 40;//dp private int min_height = 40;//dp private boolean misRect = false; public SmoothCheckBox(Context context) { this(context, null); } public SmoothCheckBox(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SmoothCheckBox(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(attrs); } private void init(AttributeSet attrs) { TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox); int tickColor = ta.getColor(R.styleable.SmoothCheckBox_smoothCheckBox_color_tick, COLOR_TICK); mAnimDuration = ta.getInt(R.styleable.SmoothCheckBox_smoothCheckBox_duration, DEF_ANIM_DURATION); mFloorColor = ta.getColor(R.styleable.SmoothCheckBox_smoothCheckBox_color_unchecked_stroke, COLOR_FLOOR_UNCHECKED); mCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_smoothCheckBox_color_checked, COLOR_CHECKED); mUnCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_smoothCheckBox_color_unchecked, COLOR_UNCHECKED); mStrokeWidth = ta.getDimensionPixelSize(R.styleable.SmoothCheckBox_smoothCheckBox_stroke_width, DimenUtils.dpToPxInt(0)); misRect = ta.getBoolean(R.styleable.SmoothCheckBox_smoothCheckBox_is_rect, misRect); ta.recycle(); mFloorUnCheckedColor = mFloorColor; mTickPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTickPaint.setStyle(Paint.Style.STROKE); mTickPaint.setStrokeCap(Paint.Cap.ROUND); mTickPaint.setColor(tickColor); mFloorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mFloorPaint.setStyle(Paint.Style.FILL); mFloorPaint.setColor(mFloorColor); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mCheckedColor); mTickPath = new Path(); mCenterPoint = new Point(); mTickPoints = new Point[3]; mTickPoints[0] = new Point(); mTickPoints[1] = new Point(); mTickPoints[2] = new Point(); setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { toggle(); mTickDrawing = false; mDrewDistance = 0; if (isChecked()) { startCheckedAnimation(); } else { startUnCheckedAnimation(); } } }); } @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable(KEY_INSTANCE_STATE, super.onSaveInstanceState()); bundle.putBoolean(KEY_INSTANCE_STATE, isChecked()); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; boolean isChecked = bundle.getBoolean(KEY_INSTANCE_STATE); setChecked(isChecked); super.onRestoreInstanceState(bundle.getParcelable(KEY_INSTANCE_STATE)); return; } super.onRestoreInstanceState(state); } @Override public boolean isChecked() { return mChecked; } @Override public void toggle() { this.setChecked(!isChecked()); } @Override public void setChecked(boolean checked) { mChecked = checked; reset(); invalidate(); if (mListener != null) { mListener.onCheckedChanged(SmoothCheckBox.this, mChecked); } } /** * <p>checked with animation</p> * * @param checked checked * @param animate change with animation */ public void setChecked(boolean checked, boolean animate) { if (animate) { mTickDrawing = false; mChecked = checked; mDrewDistance = 0f; if (checked) { startCheckedAnimation(); } else { startUnCheckedAnimation(); } if (mListener != null) { mListener.onCheckedChanged(SmoothCheckBox.this, mChecked); } } else { this.setChecked(checked); } } private void reset() { mTickDrawing = true; mFloorScale = 1.0f; mScaleVal = isChecked() ? 0f : 1.0f; mFloorColor = isChecked() ? mCheckedColor : mFloorUnCheckedColor; mDrewDistance = isChecked() ? (mLeftLineDistance + mRightLineDistance) : 0; } private int measureSize(int measureSpec) { int defSize = DimenUtils.dpToPxInt(DEF_DRAW_SIZE); int specSize = MeasureSpec.getSize(measureSpec); int specMode = MeasureSpec.getMode(measureSpec); int result = 0; switch (specMode) { case MeasureSpec.UNSPECIFIED: case MeasureSpec.AT_MOST: result = Math.min(defSize, specSize); break; case MeasureSpec.EXACTLY: result = specSize; break; } return result; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize; int heightSize; Resources r = Resources.getSystem(); if (widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST) { widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, min_width, r.getDisplayMetrics());//如果为指定大小宽最小50dp widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY); } if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST) { heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, min_height, r.getDisplayMetrics());//如果为指定大小高最小25dp heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { mWidth = getMeasuredWidth(); mStrokeWidth = (mStrokeWidth == 0 ? getMeasuredWidth() / 10 : mStrokeWidth); mStrokeWidth = mStrokeWidth > getMeasuredWidth() / 5 ? getMeasuredWidth() / 5 : mStrokeWidth; mStrokeWidth = (mStrokeWidth < 3) ? 3 : mStrokeWidth; mCenterPoint.x = mWidth / 2; mCenterPoint.y = getMeasuredHeight() / 2; mTickPoints[0].x = Math.round((float) getMeasuredWidth() / 30 * 7); mTickPoints[0].y = Math.round((float) getMeasuredHeight() / 30 * 14); mTickPoints[1].x = Math.round((float) getMeasuredWidth() / 30 * 13); mTickPoints[1].y = Math.round((float) getMeasuredHeight() / 30 * 20); mTickPoints[2].x = Math.round((float) getMeasuredWidth() / 30 * 22); mTickPoints[2].y = Math.round((float) getMeasuredHeight() / 30 * 10); mLeftLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[1].x - mTickPoints[0].x, 2) + Math.pow(mTickPoints[1].y - mTickPoints[0].y, 2)); mRightLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[2].x - mTickPoints[1].x, 2) + Math.pow(mTickPoints[2].y - mTickPoints[1].y, 2)); mTickPaint.setStrokeWidth(mStrokeWidth); } @Override protected void onDraw(Canvas canvas) { drawBorder(canvas); drawCenter(canvas); drawTick(canvas); } private void drawCenter(Canvas canvas) { mPaint.setColor(mUnCheckedColor); if (misRect) { float width = Math.min(getWidth(), getHeight()); float twmp_width = width * mScaleVal - mStrokeWidth; canvas.drawRect(width - twmp_width, width - twmp_width, twmp_width, twmp_width, mPaint); } else { float radius = (mCenterPoint.x - mStrokeWidth) * mScaleVal; canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, radius, mPaint); } } private void drawBorder(Canvas canvas) { mFloorPaint.setColor(mFloorColor); if (misRect) { float width = Math.min(getWidth(), getHeight()); float twmp_width = width * mFloorScale; canvas.drawRect(width - twmp_width, width - twmp_width, twmp_width, twmp_width, mFloorPaint); } else { int radius = mCenterPoint.x; canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, radius * mFloorScale, mFloorPaint); } } private void drawTick(Canvas canvas) { if (mTickDrawing && isChecked()) { drawTickPath(canvas); } } private void drawTickPath(Canvas canvas) { mTickPath.reset(); // draw left of the tick if (mDrewDistance < mLeftLineDistance) { float step = (mWidth / 20.0f) < 3 ? 3 : (mWidth / 20.0f); mDrewDistance += step; float stopX = mTickPoints[0].x + (mTickPoints[1].x - mTickPoints[0].x) * mDrewDistance / mLeftLineDistance; float stopY = mTickPoints[0].y + (mTickPoints[1].y - mTickPoints[0].y) * mDrewDistance / mLeftLineDistance; mTickPath.moveTo(mTickPoints[0].x, mTickPoints[0].y); mTickPath.lineTo(stopX, stopY); canvas.drawPath(mTickPath, mTickPaint); if (mDrewDistance > mLeftLineDistance) { mDrewDistance = mLeftLineDistance; } } else { mTickPath.moveTo(mTickPoints[0].x, mTickPoints[0].y); mTickPath.lineTo(mTickPoints[1].x, mTickPoints[1].y); canvas.drawPath(mTickPath, mTickPaint); // draw right of the tick if (mDrewDistance < mLeftLineDistance + mRightLineDistance) { float stopX = mTickPoints[1].x + (mTickPoints[2].x - mTickPoints[1].x) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance; float stopY = mTickPoints[1].y - (mTickPoints[1].y - mTickPoints[2].y) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance; mTickPath.reset(); mTickPath.moveTo(mTickPoints[1].x, mTickPoints[1].y); mTickPath.lineTo(stopX, stopY); canvas.drawPath(mTickPath, mTickPaint); float step = (mWidth / 20) < 3 ? 3 : (mWidth / 20); mDrewDistance += step; } else { mTickPath.reset(); mTickPath.moveTo(mTickPoints[1].x, mTickPoints[1].y); mTickPath.lineTo(mTickPoints[2].x, mTickPoints[2].y); canvas.drawPath(mTickPath, mTickPaint); } } // invalidate if (mDrewDistance < mLeftLineDistance + mRightLineDistance) { postDelayed(new Runnable() { @Override public void run() { postInvalidate(); } }, 10); } } private void startCheckedAnimation() { ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0f); animator.setDuration(mAnimDuration / 3 * 2); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mScaleVal = (float) animation.getAnimatedValue(); mFloorColor = getGradientColor(mUnCheckedColor, mCheckedColor, 1 - mScaleVal); postInvalidate(); } }); animator.start(); ValueAnimator floorAnimator = ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f); floorAnimator.setDuration(mAnimDuration); floorAnimator.setInterpolator(new LinearInterpolator()); floorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mFloorScale = (float) animation.getAnimatedValue(); postInvalidate(); } }); floorAnimator.start(); drawTickDelayed(); } private void startUnCheckedAnimation() { ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f); animator.setDuration(mAnimDuration); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mScaleVal = (float) animation.getAnimatedValue(); mFloorColor = getGradientColor(mCheckedColor, mFloorUnCheckedColor, mScaleVal); postInvalidate(); } }); animator.start(); ValueAnimator floorAnimator = ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f); floorAnimator.setDuration(mAnimDuration); floorAnimator.setInterpolator(new LinearInterpolator()); floorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mFloorScale = (float) animation.getAnimatedValue(); postInvalidate(); } }); floorAnimator.start(); } private void drawTickDelayed() { // postDelayed(new Runnable() // { // @Override // public void run() // { // mTickDrawing = true; // postInvalidate(); // } // }, mAnimDuration); mTickDrawing = true; } private static int getGradientColor(int startColor, int endColor, float percent) { int sr = (startColor & 0xff0000) >> 0x10; int sg = (startColor & 0xff00) >> 0x8; int sb = (startColor & 0xff); int er = (endColor & 0xff0000) >> 0x10; int eg = (endColor & 0xff00) >> 0x8; int eb = (endColor & 0xff); int cr = (int) (sr * (1 - percent) + er * percent); int cg = (int) (sg * (1 - percent) + eg * percent); int cb = (int) (sb * (1 - percent) + eb * percent); return Color.argb(0xff, cr, cg, cb); } public void setOnCheckedChangeListener(OnCheckedChangeListener l) { this.mListener = l; } public interface OnCheckedChangeListener { void onCheckedChanged(SmoothCheckBox checkBox, boolean isChecked); } }