Android-带有切换动画的CheckBox

时间:2021-08-15 08:39:55

转自:https://github.com/andyxialm/SmoothCheckBox

Android-带有切换动画的CheckBox

前言

切换带动画的效果确实不错,可能以后项目会用得到,所以转发收藏。

1,自定义 SmoothCheckBox 。

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_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,
CompatUtils.dp2px(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);
}
}

/**
* <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;
}

@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, COLOR_FLOOR_UNCHECKED, 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);
}
}

2,在布局文件中引用这个定义的CheckBox控件。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="cn.refactor.smoothcheckbox.MainActivity">


<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal">


<cn.refactor.library.SmoothCheckBox
android:id="@+id/mcb"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="5dp"
app:color_checked="@color/colorAccent" />


<cn.refactor.library.SmoothCheckBox
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="5dp"
app:color_checked="#447eeb" />


<cn.refactor.library.SmoothCheckBox
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="5dp"
app:color_checked="#149A45" />


<cn.refactor.library.SmoothCheckBox
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="5dp" />



</LinearLayout>

</LinearLayout>

Android-带有切换动画的CheckBox

3,使用

 setChecked(boolean checked);                   // 默认不带动画,若需要动画 调用重载方法
setChecked(boolean checked, boolean animate); // 参数: animate 是否显示动画
 protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);

final SmoothCheckBox mcb = (SmoothCheckBox) findViewById(R.id.mcb);
mcb.setOnCheckedChangeListener(new SmoothCheckBox.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(SmoothCheckBox checkBox, boolean isChecked) {
Log.d("MellowCheckBox", String.valueOf(isChecked));
}
});
}