package com.material.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.*;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.CompoundButton;
/**
* Created by IntelliJ IDEA.
* User: keith.
* Date: 14-10-8.
* Time: 15:49.
*/
public class CheckBox extends CompoundButton {
private static final long ANIMATION_DURATION = 200;
private static final float MarkRatio = 0.3f;
private static final int StateNormal = 1;
private static final int StateTouchDown = 2;
private static final int StateTouchUp = 3;
private int mState = StateNormal;
private long mStartTime;
private int mColor;
private int mCheckedColor;
private int mMarkColor;
private int mCheckBoxWidth;
private int mCheckBoxHeight;
private int mCornerRadius;
private int mThumbSize;
private int mBorderWidth;
private int mMarkWidth;
private int mRippleRadius;
private int mMarkLeftRightPadding;
private int mMarkTopPadding;
private int mMarkBottomPadding;
private Rect mFingerRect;
private boolean mMoveOutside;
private Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint thumbPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint markPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint ripplePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CheckBox(Context context) {
this(context, null);
}
public CheckBox(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CheckBox(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CheckBox);
mColor = attributes.getColor(R.styleable.CheckBox_checkbox_color,
getResources().getColor(R.color.checkbox_color));
mCheckedColor = attributes.getColor(R.styleable.CheckBox_checkbox_checked_color,
getResources().getColor(R.color.checkbox_checked_color));
attributes.recycle();
mMarkColor = getResources().getColor(R.color.checkbox_mark_color);
mCheckBoxWidth = getResources().getDimensionPixelSize(R.dimen.checkbox_width);
mCheckBoxHeight = getResources().getDimensionPixelSize(R.dimen.checkbox_height);
mCornerRadius = getResources().getDimensionPixelSize(R.dimen.checkbox_corner_radius);
mThumbSize = getResources().getDimensionPixelSize(R.dimen.checkbox_thumb_size);
mBorderWidth = getResources().getDimensionPixelSize(R.dimen.checkbox_border_width);
mMarkWidth = getResources().getDimensionPixelSize(R.dimen.checkbox_mark_width);
mRippleRadius = getResources().getDimensionPixelSize(R.dimen.checkbox_ripple_radius);
mMarkLeftRightPadding = getResources().getDimensionPixelSize(R.dimen.checkbox_mark_left_right_padding);
mMarkTopPadding = getResources().getDimensionPixelSize(R.dimen.checkbox_mark_top_padding);
mMarkBottomPadding = getResources().getDimensionPixelSize(R.dimen.checkbox_mark_bottom_padding);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size;
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthSpecMode == MeasureSpec.EXACTLY) {
if (widthSpecSize < mCheckBoxWidth) {
size = mCheckBoxWidth;
} else {
size = widthSpecSize;
}
} else {
size = mCheckBoxWidth;
}
widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightSpecMode == MeasureSpec.EXACTLY) {
if (heightSpecSize < mCheckBoxHeight) {
size = mCheckBoxHeight;
} else {
size = heightSpecSize;
}
} else {
size = mCheckBoxHeight;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private int rippleColor(int color) {
int alpha = Math.round(Color.alpha(color) * 0.3f);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
return Color.argb(alpha, red, green, blue);
}
private RectF getRectFrame() {
RectF rectF = new RectF();
rectF.left = (getWidth() - mThumbSize) / 2;
rectF.top = (getHeight() - mThumbSize) / 2;
rectF.right = (getWidth() - mThumbSize) / 2 + mThumbSize;
rectF.bottom = (getHeight() - mThumbSize) / 2 + mThumbSize;
return rectF;
}
private Path getMarkPath() {
Path path = new Path();
float x1 = (getWidth() - mThumbSize) / 2 + mMarkLeftRightPadding;
float y1 = getHeight() / 2;
float x2 = x1 + (mThumbSize - mMarkLeftRightPadding * 2) * MarkRatio;
float y2 = y1 + mThumbSize / 2 - mMarkBottomPadding;
float x3 = x1 + mThumbSize - mMarkLeftRightPadding * 2;
float y3 = (getHeight() - mThumbSize) / 2 + mMarkTopPadding;
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
return path;
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mMoveOutside = false;
mState = StateTouchDown;
mFingerRect = new Rect(getLeft(), getTop(), getRight(), getBottom());
mStartTime = System.currentTimeMillis();
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if (!mFingerRect.contains(getLeft() + (int) event.getX(),
getTop() + (int) event.getY())) {
mMoveOutside = true;
mState = StateNormal;
mStartTime = System.currentTimeMillis();
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if (!mMoveOutside) {
mState = StateTouchUp;
setChecked(!isChecked());
mStartTime = System.currentTimeMillis();
invalidate();
}
break;
case MotionEvent.ACTION_CANCEL:
mState = StateNormal;
invalidate();
break;
}
return true;
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
int rippleRadius = 0;
long elapsed = System.currentTimeMillis() - mStartTime;
switch (mState) {
case StateNormal:
break;
case StateTouchDown:
ripplePaint.setAlpha(255);
if (elapsed < ANIMATION_DURATION) {
rippleRadius = Math.round(elapsed * mRippleRadius / ANIMATION_DURATION);
} else {
rippleRadius = mRippleRadius;
}
postInvalidate();
break;
case StateTouchUp:
if (elapsed < ANIMATION_DURATION) {
int alpha = Math.round((ANIMATION_DURATION - elapsed) * 255 / ANIMATION_DURATION);
ripplePaint.setAlpha(alpha);
rippleRadius = Math.round((ANIMATION_DURATION - elapsed) * mRippleRadius / ANIMATION_DURATION);
} else {
mState = StateNormal;
rippleRadius = 0;
ripplePaint.setAlpha(0);
}
postInvalidate();
break;
}
if (isChecked()) {
ripplePaint.setColor(rippleColor(mCheckedColor));
canvas.drawCircle(getWidth() / 2, getHeight() / 2, rippleRadius, ripplePaint);
thumbPaint.setColor(mCheckedColor);
thumbPaint.setStyle(Paint.Style.FILL);
canvas.drawRoundRect(getRectFrame(), mCornerRadius, mCornerRadius, thumbPaint);
thumbPaint.setColor(mCheckedColor);
thumbPaint.setStyle(Paint.Style.STROKE);
thumbPaint.setStrokeWidth(mBorderWidth);
canvas.drawRoundRect(getRectFrame(), mCornerRadius, mCornerRadius, thumbPaint);
markPaint.setColor(mMarkColor);
markPaint.setStrokeWidth(mMarkWidth);
markPaint.setStyle(Paint.Style.STROKE);
markPaint.setStrokeJoin(Paint.Join.ROUND);
canvas.drawPath(getMarkPath(), markPaint);
} else {
ripplePaint.setColor(rippleColor(mColor));
canvas.drawCircle(getWidth() / 2, getHeight() / 2, rippleRadius, ripplePaint);
borderPaint.setColor(mColor);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(mBorderWidth);
canvas.drawRoundRect(getRectFrame(), mCornerRadius, mCornerRadius, borderPaint);
}
}
}