|
|
@@ -0,0 +1,423 @@
|
|
|
+/**
|
|
|
+ * * Copyright 2016 andy
|
|
|
+ * <p/>
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ * <p/>
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ * <p/>
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+
|
|
|
+package com.xzjmyk.pm.activity.view;
|
|
|
+
|
|
|
+import android.animation.ValueAnimator;
|
|
|
+import android.annotation.TargetApi;
|
|
|
+import android.content.Context;
|
|
|
+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.Build;
|
|
|
+import android.os.Bundle;
|
|
|
+import android.os.Parcelable;
|
|
|
+import android.util.AttributeSet;
|
|
|
+import android.view.View;
|
|
|
+import android.view.animation.LinearInterpolator;
|
|
|
+import android.widget.Checkable;
|
|
|
+
|
|
|
+import com.xzjmyk.pm.activity.R;
|
|
|
+import com.xzjmyk.pm.activity.ui.erp.util.CommonUtil;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Author : andy
|
|
|
+ * Date : 16/1/21 11:28
|
|
|
+ * Email : andyxialm@gmail.com
|
|
|
+ * Github : github.com/andyxialm
|
|
|
+ * Description : A custom CheckBox with animation for Android
|
|
|
+ */
|
|
|
+
|
|
|
+public class SmoothCheckBox extends View implements Checkable {
|
|
|
+ private static final String KEY_INSTANCE_STATE = "InstanceState";
|
|
|
+
|
|
|
+ private static final int COLOR_TICK = Color.WHITE;
|
|
|
+ private static final int COLOR_UNCHECKED = Color.WHITE;
|
|
|
+ private static final int COLOR_CHECKED = Color.parseColor("#FB4846");
|
|
|
+ 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 = 300;
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
|
+ public SmoothCheckBox(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
|
+ super(context, attrs, defStyleAttr, defStyleRes);
|
|
|
+ init(attrs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void init(AttributeSet attrs) {
|
|
|
+
|
|
|
+ TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox);
|
|
|
+ int tickColor = ta.getColor(R.styleable.SmoothCheckBox_color_tick, COLOR_TICK);
|
|
|
+ mAnimDuration = ta.getInt(R.styleable.SmoothCheckBox_duration, DEF_ANIM_DURATION);
|
|
|
+ mFloorColor = ta.getColor(R.styleable.SmoothCheckBox_color_unchecked_stroke, COLOR_FLOOR_UNCHECKED);
|
|
|
+ mCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_checked, COLOR_CHECKED);
|
|
|
+ mUnCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_unchecked, COLOR_UNCHECKED);
|
|
|
+ mStrokeWidth = ta.getDimensionPixelSize(R.styleable.SmoothCheckBox_stroke_width, CommonUtil.convertDip2Px(getContext(), 0));
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * checked with animation
|
|
|
+ * @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 = CommonUtil.convertDip2Px(getContext(), 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) {
|
|
|
+ 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);
|
|
|
+ float radius = (mCenterPoint.x - mStrokeWidth) * mScaleVal;
|
|
|
+ canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, radius, mPaint);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawBorder(Canvas canvas) {
|
|
|
+ mFloorPaint.setColor(mFloorColor);
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+}
|