首先贴出实现的效果图:
gif的效果可能有点过快,在真机上运行的效果会更好一些。我们主要的思路就是利用属性动画来动态地画出选中状态以及对勾的绘制过程。看到上面的效果图,相信大家都迫不及待地要跃跃欲试了,那就让我们开始吧。
自定义View的第一步:自定义属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
|
我们把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) {
|