Android实现炫酷的CheckBox效果

xiaoxiao2021-02-27  249

首先贴出实现的效果图:

gif的效果可能有点过快,在真机上运行的效果会更好一些。我们主要的思路就是利用属性动画来动态地画出选中状态以及对勾的绘制过程。看到上面的效果图,相信大家都迫不及待地要跃跃欲试了,那就让我们开始吧。

自定义View的第一步:自定义属性。

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version= "1.0" encoding= "utf-8" ?> <resources>   <declare-styleable name= "SmoothCheckBox" >   <!-- 动画持续时间 -->   <attr name= "duration" format= "integer" ></attr>   <!-- 边框宽度 -->   <attr name= "strikeWidth" format= "dimension|reference" ></attr>   <!-- 边框颜色 -->   <attr name= "borderColor" format= "color|reference" ></attr>   <!-- 选中状态的颜色 -->   <attr name= "trimColor" format= "color|reference" ></attr>   <!-- 对勾颜色 -->   <attr name= "tickColor" format= "color|reference" ></attr>   <!-- 对勾宽度 -->   <attr name= "tickWidth" format= "dimension|reference" ></attr>   </declare-styleable> </resources>

我们把CheckBox取名为SmoothCheckBox,定义了几个等等要用到的属性。这一步很简单,相信大家都熟练了。

接下来看一看onMeasure(int widthMeasureSpec, int heightMeasureSpec):

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {   super .onMeasure(widthMeasureSpec, heightMeasureSpec);   int widthSize = MeasureSpec.getSize(widthMeasureSpec);   int widthMode = MeasureSpec.getMode(widthMeasureSpec);   if (widthMode == MeasureSpec.EXACTLY) {    mWidth = widthSize;   } else {    mWidth = 40 ;   }     int heightSize = MeasureSpec.getSize(heightMeasureSpec);   int heightMode = MeasureSpec.getMode(heightMeasureSpec);   if (heightMode == MeasureSpec.EXACTLY) {    mHeight = heightSize;   } else {    mHeight = 40 ;   }   setMeasuredDimension(mWidth, mHeight);   int size = Math.min(mWidth, mHeight);   center = size / 2 ;   mRadius = ( int ) ((size - mStrokeWidth) / 2 / 1 .2f);   startPoint.set(center * 14 / 30 , center * 28 / 30 );   breakPoint.set(center * 26 / 30 , center * 40 / 30 );   endPoint.set(center * 44 / 30 , center * 20 / 30 );     downLength = ( float ) Math.sqrt(Math.pow(startPoint.x - breakPoint.x, 2f) + Math.pow(startPoint.y - breakPoint.y, 2f));   upLength = ( float ) Math.sqrt(Math.pow(endPoint.x - breakPoint.x, 2f) + Math.pow(endPoint.y - breakPoint.y, 2f));   totalLength = downLength + upLength; }

一开始是测量了SmoothCheckBox的宽、高度,默认的宽高度随便定义了一个,当然你们可以自己去修改和完善它。然后就是设置半径之类的,最后的startPoint、breakPoint、endPoint分别对应着选中时对勾的三个点(至于为何是这几个数字,那完全是经验值);downLength就是startPoint和breakPoint的距离,而相对应的upLength就是breakPoint和endPoint的距离。即以下图示:

在看onDraw(Canvas canvas)之前我们先来看两组动画,分别是选中状态时的动画以及未选中状态的动画:

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 // 由未选中到选中的动画 private void checkedAnimation() {   animatedValue = 0f;   tickValue = 0f;   // 选中时底色的动画   mValueAnimator = ValueAnimator.ofFloat(0f, 1 .2f, 1f).setDuration( 2 * duration / 5 );   mValueAnimator.setInterpolator( new AccelerateDecelerateInterpolator());   // 对勾的动画   mTickValueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration( 3 * duration / 5 );   mTickValueAnimator.setInterpolator( new LinearInterpolator());   mTickValueAnimator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {    @Override    public void onAnimationUpdate(ValueAnimator valueAnimator) {   // 得到动画执行进度     tickValue = ( float ) valueAnimator.getAnimatedValue();     postInvalidate();    }   });   mValueAnimator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {    @Override    public void onAnimationUpdate(ValueAnimator valueAnimator) {   // 得到动画执行进度     animatedValue = ( float ) valueAnimator.getAnimatedValue();     postInvalidate();    }   });   mValueAnimator.addListener( new AnimatorListenerAdapter() {    @Override    public void onAnimationEnd(Animator animation) {   //当底色的动画完成后再开始对勾的动画     mTickValueAnimator.start();     Log.i(TAG, " mTickValueAnimator.start();" );    }   });   mValueAnimator.start(); }   // 由选中到未选中的动画 private void uncheckedAnimation() {   animatedValue = 0f;   mValueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration( 2 * duration / 5 );   mValueAnimator.setInterpolator( new AccelerateInterpolator());   mValueAnimator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {    @Override    public void onAnimationUpdate(ValueAnimator valueAnimator) {     animatedValue = ( float ) valueAnimator.getAnimatedValue();     postInvalidate();    }   });   mValueAnimator.start(); }

这两组动画在点击SmoothCheckBox的时候会调用。相似的,都是在动画执行中得到动画执行的进度,再来调用postInvalidate();让SmoothCheckBox重绘。看完这个之后就是终极大招onDraw(Canvas canvas)了:

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @Override protected void onDraw(Canvas canvas) {   super .onDraw(canvas);   canvas.save();   drawBorder(canvas);   drawTrim(canvas);   if (isChecked) {    drawTick(canvas);   }   canvas.restore(); }   // 画对勾 private void drawTick(Canvas canvas) {   // 得到画对勾的进度   float temp = tickValue * totalLength;   Log.i(TAG, "temp:" + temp + "downlength :" + downLength);   //判断是否是刚开始画对勾的时候,即等于startPoint   if (Float.compare(tickValue, 0f) == 0 ) {    Log.i(TAG, "startPoint : " + startPoint.x + ", " + startPoint.y);    path.reset();    path.moveTo(startPoint.x, startPoint.y);   }   // 如果画对勾的进度已经超过breakPoint的时候,即(breakPoint,endPoint]   if (temp > downLength) {    path.moveTo(startPoint.x, startPoint.y);    path.lineTo(breakPoint.x, breakPoint.y);    Log.i(TAG, "endPoint : " + endPoint.x + ", " + endPoint.y);    path.lineTo((endPoint.x - breakPoint.x) * (temp - downLength) / upLength + breakPoint.x, (endPoint.y - breakPoint.y) * (temp - downLength) / upLength + breakPoint.y);   } else {   //画对勾的进度介于startPoinit和breakPoint之间,即(startPoint,breakPoint]    Log.i(TAG, "down x : " + (breakPoint.x - startPoint.x) * temp / downLength + ",down y: " + (breakPoint.y - startPoint.y) * temp / downLength);    path.lineTo((breakPoint.x - startPoint.x) * temp / downLength + startPoint.x, (breakPoint.y - startPoint.y) * temp / downLength + startPoint.y);   }   canvas.drawPath(path, tickPaint); }   // 画边框 private void drawBorder(Canvas canvas) {   float temp;   // 通过animatedValue让边框产生一个“OverShooting”的动画   if (animatedValue > 1f) {    temp = animatedValue * mRadius;   } else {    temp = mRadius;   }   canvas.drawCircle(center, center, temp, borderPaint); }   // 画checkbox内部 private void drawTrim(Canvas canvas) {   canvas.drawCircle(center, center, (mRadius - mStrokeWidth) * animatedValue, trimPaint); }

onDraw(Canvas canvas)代码中的逻辑基本都加了注释,主要就是原理搞懂了就比较简单了。在绘制对勾时要区分当前处于绘制对勾的哪种状态,然后对应做处理画出线条,剩下的就简单了。关于SmoothCheckBox的讲解到这里就差不多了。

下面就贴出SmoothCheckBox的完整代码:

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 public class SmoothCheckBox extends View implements View.OnClickListener {     // 动画持续时间   private long duration;   // 边框宽度   private float mStrokeWidth;   // 对勾宽度   private float mTickWidth;   // 内饰画笔   private Paint trimPaint;   // 边框画笔   private Paint borderPaint;   // 对勾画笔   private Paint tickPaint;   // 默认边框宽度   private float defaultStrikeWidth;   // 默认对勾宽度   private float defaultTickWidth;   // 宽度   private int mWidth;   // 高度   private int mHeight;   // 边框颜色   private int borderColor;   // 内饰颜色   private int trimColor;   // 对勾颜色   private int tickColor;   // 半径   private int mRadius;   // 中心点   private int center;   // 是否是选中   private boolean isChecked;   //对勾向下的长度   private float downLength;   //对勾向上的长度   private float upLength;   // 对勾的总长度   private float totalLength;   // 监听器   private OnCheckedChangeListener listener;     private ValueAnimator mValueAnimator;     private ValueAnimator mTickValueAnimator;     private float animatedValue;     private float tickValue;   // 对勾开始点   private Point startPoint = new Point();   // 对勾转折点   private Point breakPoint = new Point();   // 对勾结束点   private Point endPoint = new Point();     private static final String TAG = "SmoothCheckBox" ;     private static final String KEY_INSTANCE_STATE = "InstanceState" ;     private Path path = new Path();     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {    this .listener = listener;   }     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);    TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox);    duration = a.getInt(R.styleable.SmoothCheckBox_duration, 600 );      defaultStrikeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1 , getResources().getDisplayMetrics());    mStrokeWidth = a.getDimension(R.styleable.SmoothCheckBox_strikeWidth, defaultStrikeWidth);    defaultTickWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2 , getResources().getDisplayMetrics());    mTickWidth = a.getDimension(R.styleable.SmoothCheckBox_tickWidth, defaultTickWidth);    borderColor = a.getColor(R.styleable.SmoothCheckBox_borderColor, getResources().getColor(android.R.color.darker_gray));    trimColor = a.getColor(R.styleable.SmoothCheckBox_trimColor, getResources().getColor(android.R.color.holo_green_light));    tickColor = a.getColor(R.styleable.SmoothCheckBox_tickColor, getResources().getColor(android.R.color.white));    a.recycle();      trimPaint = new Paint(Paint.ANTI_ALIAS_FLAG);    trimPaint.setStyle(Paint.Style.FILL);    trimPaint.setColor(trimColor);      borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);    borderPaint.setStrokeWidth(mStrokeWidth);    borderPaint.setColor(borderColor);    borderPaint.setStyle(Paint.Style.STROKE);      tickPaint = new Paint(Paint.ANTI_ALIAS_FLAG);    tickPaint.setColor(tickColor);    tickPaint.setStyle(Paint.Style.STROKE);    tickPaint.setStrokeCap(Paint.Cap.ROUND);    tickPaint.setStrokeWidth(mTickWidth);      setOnClickListener( this );   }     @Override   protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {    super .onMeasure(widthMeasureSpec, heightMeasureSpec);    int widthSize = MeasureSpec.getSize(widthMeasureSpec);    int widthMode = MeasureSpec.getMode(widthMeasureSpec);    if (widthMode == MeasureSpec.EXACTLY) {     mWidth = widthSize;    } else {     mWidth = 40 ;    }      int heightSize = MeasureSpec.getSize(heightMeasureSpec);    int heightMode = MeasureSpec.getMode(heightMeasureSpec);    if (heightMode == MeasureSpec.EXACTLY) {     mHeight = heightSize;    } else {     mHeight = 40 ;    }    setMeasuredDimension(mWidth, mHeight);    int size = Math.min(mWidth, mHeight);    center = size / 2 ;    mRadius = ( int ) ((size - mStrokeWidth) / 2 / 1 .2f);    startPoint.set(center * 14 / 30 , center * 28 / 30 );    breakPoint.set(center * 26 / 30 , center * 40 / 30 );    endPoint.set(center * 44 / 30 , center * 20 / 30 );      downLength = ( float ) Math.sqrt(Math.pow(startPoint.x - breakPoint.x, 2f) + Math.pow(startPoint.y - breakPoint.y, 2f));    upLength = ( float ) Math.sqrt(Math.pow(endPoint.x - breakPoint.x, 2f) + Math.pow(endPoint.y - breakPoint.y, 2f));    totalLength = downLength + upLength;   }     @Override   protected void onDraw(Canvas canvas) {    super .onDraw(canvas);    canvas.save();    drawBorder(canvas);    drawTrim(canvas);    if (isChecked) {     drawTick(canvas);    }    canvas.restore();   }     @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);   }     // 切换状态   private void toggle() {    isChecked = !isChecked;    if (listener != null ) {     listener.onCheckedChanged( this , isChecked);    }    if (isChecked) {     checkedAnimation();    } else {     uncheckedAnimation();    }   }     // 由未选中到选中的动画   private void checkedAnimation() {    animatedValue = 0f;    tickValue = 0f;    mValueAnimator = ValueAnimator.ofFloat(0f, 1 .2f, 1f).setDuration( 2 * duration / 5 );    mValueAnimator.setInterpolator( new AccelerateDecelerateInterpolator());    mTickValueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration( 3 * duration / 5 );    mTickValueAnimator.setInterpolator( new LinearInterpolator());    mTickValueAnimator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {     @Override     public void onAnimationUpdate(ValueAnimator valueAnimator) {      tickValue = ( float ) valueAnimator.getAnimatedValue();      postInvalidate();     }    });    mValueAnimator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {     @Override     public void onAnimationUpdate(ValueAnimator valueAnimator) {      animatedValue = ( float ) valueAnimator.getAnimatedValue();      postInvalidate();     }    });    mValueAnimator.addListener( new AnimatorListenerAdapter() {     @Override     public void onAnimationEnd(Animator animation) {      mTickValueAnimator.start();      Log.i(TAG, " mTickValueAnimator.start();" );     }    });    mValueAnimator.start();   }     // 由选中到未选中的动画   private void uncheckedAnimation() {    animatedValue = 0f;    mValueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration( 2 * duration / 5 );    mValueAnimator.setInterpolator( new AccelerateInterpolator());    mValueAnimator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {     @Override     public void onAnimationUpdate(ValueAnimator valueAnimator) {      animatedValue = ( float ) valueAnimator.getAnimatedValue();      postInvalidate();     }    });    mValueAnimator.start();   }     // 画对勾   private void drawTick(Canvas canvas) {    float temp = tickValue * totalLength;    Log.i(TAG, "temp:" + temp + "downlength :" + downLength);    if (Float.compare(tickValue, 0f) == 0 ) {     Log.i(TAG, "startPoint : " + startPoint.x + ", " + startPoint.y);     path.reset();     path.moveTo(startPoint.x, startPoint.y);    }    if (temp > downLength) {     path.moveTo(startPoint.x, startPoint.y);     path.lineTo(breakPoint.x, breakPoint.y);     Log.i(TAG, "endPoint : " + endPoint.x + ", " + endPoint.y);     path.lineTo((endPoint.x - breakPoint.x) * (temp - downLength) / upLength + breakPoint.x, (endPoint.y - breakPoint.y) * (temp - downLength) / upLength + breakPoint.y);    } else {     Log.i(TAG, "down x : " + (breakPoint.x - startPoint.x) * temp / downLength + ",down y: " + (breakPoint.y - startPoint.y) * temp / downLength);     path.lineTo((breakPoint.x - startPoint.x) * temp / downLength + startPoint.x, (breakPoint.y - startPoint.y) * temp / downLength + startPoint.y);    }    canvas.drawPath(path, tickPaint);   }     // 画边框   private void drawBorder(Canvas canvas) {    float temp;    if (animatedValue > 1f) {     temp = animatedValue * mRadius;    } else {     temp = mRadius;    }    canvas.drawCircle(center, center, temp, borderPaint);   }     // 画checkbox内部   private void drawTrim(Canvas canvas) {    canvas.drawCircle(center, center, (mRadius - mStrokeWidth) * animatedValue, trimPaint);   }     @Override   public void onClick(View view) {    toggle();   }     /**    * 判断checkbox是否选中状态    *    * @return    */   public boolean isChecked() {    return isChecked;   }     /**    * 设置checkbox的状态    *    * @param isChecked 是否选中    */   public void setChecked( boolean isChecked) {    this .setChecked(isChecked, false );   }     /**    * 设置checkbox的状态    *    * @param isChecked 是否选中    * @param isAnimation 切换时是否有动画    */   public void setChecked( boolean isChecked, boolean isAnimation) {    this .isChecked = isChecked;    if (isAnimation) {     if (isChecked) {      checkedAnimation();     } else {      uncheckedAnimation();     }    } else {     animatedValue = isChecked ? 1f : 0f;     tickValue = 1f;     invalidate();    }    if (listener != null ) {     listener.onCheckedChanged( this , isChecked);    }   }     public interface OnCheckedChangeListener {    void onCheckedChanged(SmoothCheckBox smoothCheckBox, boolean isChecked);   } }
转载请注明原文地址: https://www.6miu.com/read-3221.html

最新回复(0)