不想学习想要娱乐的时候忽然看到了这个动画效果,出于程序员的本能,就仿出来了。
闲话不多说,开整。
我们看实现的动画效果,其实是分为
1. 绘制未选中状态图形(圆弧和对号)
2. 绘制选中状态圆弧的旋转的动画
3. 绘制选中状态圆弧向中心收缩铺满动画
4. 绘制选中状态对号
5. 绘制选中状态下圆的放大回弹动画
6. 暴露接口接口回调传递选中未选中状态
我们一步一步来实现
首先我们完成准备工作
自定义属性attrs.xml
获取自定义属性并初始化画笔
private int mCustomSize;//画布大小
private int mRadius;
private int mCheckBaseColor;//选中状态基本颜色
private int mCheckTickColor;//选中状态对号颜色
private int mUnCheckTickColor;//未选中状态对号颜色
private int mUnCheckBaseColor;//未选中状态基本颜色
private Paint mCheckPaint;//选中状态画笔 下面的背景圆
private Paint mCheckArcPaint;//选中状态画笔 下面的背景圆圆弧
private Paint mCheckDeclinePaint;//选中状态画笔 (上面的随动画缩减的圆盖在上面) 和对号
private Paint mUnCheckPaint;//未选中状态画笔
private Paint mCheckTickPaint;//选中对号画笔
private Paint mCheckPaintArc;//回弹圆画笔 设置不同宽度已达到回弹圆动画目的
private boolean isCheckd = false;//选中状态
private float[] mPoints;
private int mCenter;
/**
* 获取自定义属性
*
* @param context
* @param attrs
*/
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomTickView);
mCustomSize = (int) typedArray.getDimension(R.styleable.CustomTickView_custom_size, dip2px(130));
mCheckBaseColor = typedArray.getColor(R.styleable.CustomTickView_check_base_color, mCheckBaseColor);
mCheckTickColor = typedArray.getColor(R.styleable.CustomTickView_check_tick_color, mCheckTickColor);
mUnCheckBaseColor = typedArray.getColor(R.styleable.CustomTickView_uncheck_base_color, mUnCheckBaseColor);
mUnCheckTickColor = typedArray.getColor(R.styleable.CustomTickView_uncheck_tick_color, mUnCheckTickColor);
typedArray.recycle();
mCenter = mCustomSize / 2;
mRadius = mCenter - 50;//缩小圆半径大小 防止回弹动画弹出画布
}
/***
* 初始化画笔
*/
private void initPaint() {
mCheckPaint = new Paint();
mCheckPaint.setAntiAlias(true);
mCheckPaint.setColor(mCheckBaseColor);
mCheckPaintArc = new Paint();
mCheckPaintArc.setAntiAlias(true);
mCheckPaintArc.setColor(mCheckBaseColor);
mCheckArcPaint = new Paint();
mCheckArcPaint.setAntiAlias(true);
mCheckArcPaint.setColor(mCheckBaseColor);
mCheckArcPaint.setStyle(Paint.Style.STROKE);
mCheckArcPaint.setStrokeWidth(20);
mCheckDeclinePaint = new Paint();
mCheckDeclinePaint.setAntiAlias(true);
mCheckDeclinePaint.setColor(Color.parseColor("#3E3E3E"));
mUnCheckPaint = new Paint();
mUnCheckPaint.setAntiAlias(true);
mUnCheckPaint.setColor(mUnCheckBaseColor);
mUnCheckPaint.setStyle(Paint.Style.STROKE);
mUnCheckPaint.setStrokeWidth(20);
mCheckTickPaint = new Paint();
mCheckTickPaint.setAntiAlias(true);
mCheckTickPaint.setColor(mCheckTickColor);
mCheckTickPaint.setStyle(Paint.Style.STROKE);
mCheckTickPaint.setStrokeWidth(20);
}
测量布局
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mCustomSize, mCustomSize);//正方形布局长宽一致
}
准备工作完成,接下来我们按照刚才的步骤一步一步实现
@Override
protected void onDraw(Canvas canvas) {
if (mCustomSize > 0) {
if (!isCheckd) {
canvas.drawCircle(mCenter, mCenter, mRadius, mUnCheckPaint);//未选中状态的圆
canvas.drawLines(mPoints, mUnCheckPaint);
return;
}
}
}
@Override
protected void onDraw(Canvas canvas) {
//省略以上代码
mRingCounter += 10;//按照如下速率转动
if (mRingCounter >= 360) {
mRingCounter = 360;
}
canvas.drawArc(mRectF, 90, mRingCounter, false, mCheckArcPaint);
}
这里向中心收缩的动画我们可以逆向思维
先绘制指定颜色的整体圆,然后在指定颜色整体圆的图层上在绘制一个背景色的圆(半径不断缩小) 半径不断缩小,背景就不断露出来,达到向中心收缩的效果。
@Override
protected void onDraw(Canvas canvas) {
//省略以上代码
if (mRingCounter == 360) {
//先绘制指定颜色的圆
canvas.drawCircle(mCenter, mCenter, mRadius, mCheckPaint);
//然后在指定颜色的图层上,再绘制背景色的圆(半径不断缩小) 半径不断缩小,背景就不断露出来,达到向中心收缩的效果
mCircleCounter += 10;
canvas.drawCircle(mCenter, mCenter, mRadius - mCircleCounter, mCheckDeclinePaint);
}
我们让对号出现的有延迟效果并加上透明出现的效果
@Override
protected void onDraw(Canvas canvas) {
//省略以上代码
if (mCircleCounter >= mRadius + 100) {//做延迟效果
mAlphaCount += 20;
if (mAlphaCount >= 255) mAlphaCount = 255; //显示对号(外加一个透明的渐变)
mCheckTickPaint.setAlpha(mAlphaCount);//设置透明度
//画白色的对号
canvas.drawLines(mPoints, mCheckTickPaint);
}
}
这里我们的实现思路是在整体圆图层上在画一个圆弧,圆弧的宽度由增大到缩小最后直至为0,这样达到放大回弹的效果
@Override
protected void onDraw(Canvas canvas) {
if (mCustomSize > 0) {
if (!isCheckd) {
canvas.drawCircle(mCenter, mCenter, mRadius, mUnCheckPaint);//未选中状态的圆
canvas.drawLines(mPoints, mUnCheckPaint);
return;
}
mRingCounter += 10;
if (mRingCounter >= 360) {
mRingCounter = 360;
}
canvas.drawArc(mRectF, 90, mRingCounter, false, mCheckArcPaint);
if (mRingCounter == 360) {
//先绘制指定颜色的圆
canvas.drawCircle(mCenter, mCenter, mRadius, mCheckPaint);
//然后在指定颜色的图层上,再绘制背景色的圆(半径不断缩小) 半径不断缩小,背景就不断露出来,达到向中心收缩的效果
mCircleCounter += 10;
canvas.drawCircle(mCenter, mCenter, mRadius - mCircleCounter, mCheckDeclinePaint);
if (mCircleCounter >= mRadius + 100) {
mAlphaCount += 20;
if (mAlphaCount >= 255) mAlphaCount = 255; //显示对号(外加一个透明的渐变)
mCheckTickPaint.setAlpha(mAlphaCount);//设置透明度
//画白色的对号
canvas.drawLines(mPoints, mCheckTickPaint);
scaleCounter -= 4;//获取是否回弹
if (scaleCounter <= -50) {//scaleCounter从大于0到小于0的过程中 画笔宽度也是由增加到减少最后减为0 实现了圆放大收缩的回弹效果
scaleCounter = -50;
}
//放大并回弹,设置画笔的宽度
float strokeWith = mCheckArcPaint.getStrokeWidth() +
(scaleCounter > 0 ? 6 : -6);
System.out.println(strokeWith);
mCheckArcPaint.setStrokeWidth(strokeWith);
canvas.drawArc(mRectArc, 90, 360, false, mCheckArcPaint);
}
}
postInvalidate();//重绘
}
}
以上我们就实现了所有的动画效果
下面我们需要定义接口并暴露接口
/**
* 初始化点击事件
*/
public void setUpEvent() {
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
isCheckd = !isCheckd;
reset();
if (mOnCheckedChangeListener != null) {
//此处回调
mOnCheckedChangeListener.onCheckedChanged((CustomTickView) view, isCheckd);
}
}
});
}
private OnCheckedChangeListener mOnCheckedChangeListener;
public interface OnCheckedChangeListener {
void onCheckedChanged(CustomTickView tickView, boolean isCheckd);
}
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
this.mOnCheckedChangeListener = listener;
}
现在我们就可以通过点击自定义控件实现动画并且接受选中状态
customTickView.setUpEvent();//运行动画
customTickView.setOnCheckedChangeListener(new CustomTickView.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CustomTickView tickView, boolean isCheckd) {
if(!isCheckd){
tv_show.setText("未完成签到");
}else{
tv_show.setText("已签到");
}
}
});
附上所有代码:
CustomTickView.java
/**
* @description:TODO
* @Author MRyan
* @Date 2020/2/29 16:17
* @Version 1.0
*/
public class CustomTickView extends View {
private int mCustomSize;//画布大小
private int mRadius;
private int mCheckBaseColor;//选中状态基本颜色
private int mCheckTickColor;//选中状态对号颜色
private int mUnCheckTickColor;//未选中状态对号颜色
private int mUnCheckBaseColor;//未选中状态基本颜色
private Paint mCheckPaint;//选中状态画笔 下面的背景圆
private Paint mCheckArcPaint;//选中状态画笔 下面的背景圆圆弧
private Paint mCheckDeclinePaint;//选中状态画笔 (上面的随动画缩减的圆盖在上面) 和对号
private Paint mUnCheckPaint;//未选中状态画笔
private Paint mCheckTickPaint;//选中对号画笔
private Paint mCheckPaintArc;//回弹圆画笔 设置不同宽度已达到回弹圆动画目的
private boolean isCheckd = false;//选中状态
private float[] mPoints;
private int mCenter;
private RectF mRectF;
private int mRingCounter;
private int mCircleCounter = 0;//盖在上面的背景色圆逐渐缩小 逆向思维模拟向圆心收缩动画
private int mAlphaCount = 0;
private int scaleCounter = 50;
private RectF mRectArc;
public CustomTickView(Context context) {
super(context);
}
public CustomTickView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);//获取自定义属性
initPaint();//初始化画笔
}
public CustomTickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, 0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mCustomSize, mCustomSize);
}
@Override
protected void onDraw(Canvas canvas) {
if (mCustomSize > 0) {
if (!isCheckd) {
canvas.drawCircle(mCenter, mCenter, mRadius, mUnCheckPaint);//未选中状态的圆
canvas.drawLines(mPoints, mUnCheckPaint);
return;
}
mRingCounter += 10;
if (mRingCounter >= 360) {
mRingCounter = 360;
}
canvas.drawArc(mRectF, 90, mRingCounter, false, mCheckArcPaint);
if (mRingCounter == 360) {
//先绘制指定颜色的圆
canvas.drawCircle(mCenter, mCenter, mRadius, mCheckPaint);
//然后在指定颜色的图层上,再绘制背景色的圆(半径不断缩小) 半径不断缩小,背景就不断露出来,达到向中心收缩的效果
mCircleCounter += 10;
canvas.drawCircle(mCenter, mCenter, mRadius - mCircleCounter, mCheckDeclinePaint);
if (mCircleCounter >= mRadius + 100) {
mAlphaCount += 20;
if (mAlphaCount >= 255) mAlphaCount = 255; //显示对号(外加一个透明的渐变)
mCheckTickPaint.setAlpha(mAlphaCount);//设置透明度
//画白色的对号
canvas.drawLines(mPoints, mCheckTickPaint);
scaleCounter -= 4;//获取是否回弹
if (scaleCounter <= -50) {//scaleCounter从大于0到小于0的过程中 画笔宽度也是由增加到减少最后减为0 实现了圆放大收缩的回弹效果
scaleCounter = -50;
}
//放大并回弹,设置画笔的宽度
float strokeWith = mCheckArcPaint.getStrokeWidth() +
(scaleCounter > 0 ? 6 : -6);
System.out.println(strokeWith);
mCheckArcPaint.setStrokeWidth(strokeWith);
canvas.drawArc(mRectArc, 90, 360, false, mCheckArcPaint);
}
}
postInvalidate();//重绘
}
}
/**
* 获取自定义属性
*
* @param context
* @param attrs
*/
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomTickView);
mCustomSize = (int) typedArray.getDimension(R.styleable.CustomTickView_custom_size, dip2px(130));
mCheckBaseColor = typedArray.getColor(R.styleable.CustomTickView_check_base_color, mCheckBaseColor);
mCheckTickColor = typedArray.getColor(R.styleable.CustomTickView_check_tick_color, mCheckTickColor);
mUnCheckBaseColor = typedArray.getColor(R.styleable.CustomTickView_uncheck_base_color, mUnCheckBaseColor);
mUnCheckTickColor = typedArray.getColor(R.styleable.CustomTickView_uncheck_tick_color, mUnCheckTickColor);
typedArray.recycle();
mCenter = mCustomSize / 2;
mRadius = mCenter - 50;//缩小圆半径大小 防止回弹动画弹出画布
mPoints = new float[8];
//简易模拟对号 未做适配
mPoints[0] = mCenter - mCenter / 3;
mPoints[1] = mCenter;
mPoints[2] = mCenter;
mPoints[3] = mCenter + mCenter / 4;
mPoints[4] = mCenter - 8;
mPoints[5] = mCenter + mCenter / 4;
mPoints[6] = mCenter + mCenter / 2;
mPoints[7] = mCenter - mCenter / 5;
mRectF = new RectF(mCenter - mRadius, mCenter - mRadius, mCenter + mRadius, mCenter + mRadius);//选中状态的圆弧 动画
mRectArc = new RectF(mCenter - mRadius, mCenter - mRadius, mCenter + mRadius, mCenter + mRadius);//选中状态的圆弧 动画
}
/***
* 初始化画笔
*/
private void initPaint() {
mCheckPaint = new Paint();
mCheckPaint.setAntiAlias(true);
mCheckPaint.setColor(mCheckBaseColor);
mCheckPaintArc = new Paint();
mCheckPaintArc.setAntiAlias(true);
mCheckPaintArc.setColor(mCheckBaseColor);
mCheckArcPaint = new Paint();
mCheckArcPaint.setAntiAlias(true);
mCheckArcPaint.setColor(mCheckBaseColor);
mCheckArcPaint.setStyle(Paint.Style.STROKE);
mCheckArcPaint.setStrokeWidth(20);
mCheckDeclinePaint = new Paint();
mCheckDeclinePaint.setAntiAlias(true);
mCheckDeclinePaint.setColor(Color.parseColor("#3E3E3E"));
mUnCheckPaint = new Paint();
mUnCheckPaint.setAntiAlias(true);
mUnCheckPaint.setColor(mUnCheckBaseColor);
mUnCheckPaint.setStyle(Paint.Style.STROKE);
mUnCheckPaint.setStrokeWidth(20);
mCheckTickPaint = new Paint();
mCheckTickPaint.setAntiAlias(true);
mCheckTickPaint.setColor(mCheckTickColor);
mCheckTickPaint.setStyle(Paint.Style.STROKE);
mCheckTickPaint.setStrokeWidth(20);
}
/**
* 重置
*/
private void reset() {
mRingCounter = 0;
mCircleCounter = 0;
mAlphaCount = 0;
scaleCounter = 50;
mCheckArcPaint.setStrokeWidth(20); //画笔宽度重置
postInvalidate();
}
/**
* dp转px
*
* @param dpValue
* @return
*/
public int dip2px(float dpValue) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 初始化点击事件
*/
public void setUpEvent() {
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
isCheckd = !isCheckd;
reset();
if (mOnCheckedChangeListener != null) {
//此处回调
mOnCheckedChangeListener.onCheckedChanged((CustomTickView) view, isCheckd);
}
}
});
}
private OnCheckedChangeListener mOnCheckedChangeListener;
public interface OnCheckedChangeListener {
void onCheckedChanged(CustomTickView tickView, boolean isCheckd);
}
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
this.mOnCheckedChangeListener = listener;
}
}
activity_main.java
MainActivity.java
public class MainActivity extends AppCompatActivity {
private CustomTickView customTickView;
private TextView tv_show;
@SuppressLint("ObsoleteSdkInt")
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window window = getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_show = findViewById(R.id.tv_show);
customTickView = findViewById(R.id.customTickView);
customTickView.setUpEvent();//运行动画
customTickView.setOnCheckedChangeListener(new CustomTickView.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CustomTickView tickView, boolean isCheckd) {
if(!isCheckd){
tv_show.setText("未完成签到");
}else{
tv_show.setText("已签到");
}
}
});
}
}
项目源码