神奇的水滴效果导航栏-BezierIndicator

很早之前就看见过这样一个特效

心怡很久,却一直恐于自定义View这座大山。最近在突击自定义View的技能,学习贝塞尔曲线的绘制,前面搞了个很简单的MagicButton,甚是兴奋😄 所以斗胆来试试看实现这个特效。

分析

找了半天终于找到当初看见的这个特效的原博客 –三次贝塞尔曲线练习之弹性的圆

另外在评论中发现竟然有人已经实现了这个自定义View了–自定义View之炫酷的水滴ViewPageIndicator,效果很不错,借鉴之😄

关于最核心的贝塞尔小球动效的绘制,博主进行了很详细的解析及描述,并且提供了一个demo,万分感谢😄

这里简单回顾一下这个小球的绘制过程:

为了控制小球的不同形态,我们这里使用三阶贝塞尔曲线cubicTo来绘制小球。





而小球一共可以分成5个状态来绘制



状态1

状态1

状态2

状态2

状态3

状态3

状态4

状态4

状态5

状态5

绘制

计算控件宽高

作为一个导航控件,我暂时不考虑宽度设置为warp_content的状态,设置wrap_content一律计算为屏幕的最大宽高.

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
    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

WindowManager wm = (WindowManager) getContext()
.getSystemService(Context.WINDOW_SERVICE);
/**
* 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

if (widthMode == MeasureSpec.EXACTLY) {
width = sizeWidth;
} else {
width = wm.getDefaultDisplay().getWidth();
}

if (heightMode == MeasureSpec.EXACTLY) {
height = sizeHeight;
} else {
height = wm.getDefaultDisplay().getHeight();
}

if (getChildCount() != 0) {
childSideLength = (width - getPaddingRight() - getPaddingLeft()) / getChildCount() > height - getPaddingBottom() - getPaddingTop() ? height - getPaddingBottom() - getPaddingTop() : (width - getPaddingLeft() - getPaddingRight()) / getChildCount();
// //计算出所有的ChildView的宽和高
// measureChildren(widthMeasureSpec, heightMeasureSpec);
bezierCircular = new BezierCircular(childSideLength / 2);
}

setMeasuredDimension(width, height);
}

计算子控件的位置

为了方便管理,子View的大小统一计算为一个正方形区域,设置一个子View的padding值childPadding,可以通过childPadding值控制我们添加的子view呈现出的大小,也就是效果图中小图标在白色圆环中的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
if (childCount == 0) {
return;
}
//相邻两个子View中心点的间距
float childDis = (width - getPaddingLeft() - getPaddingRight() - 2 * defaultLeftRightGap - childSideLength) / (childCount - 1);
float cWidth = childSideLength - 2 * childPadding;
float cHeight = cWidth;

anchorList.clear();
//计算子控件的位置,强制将子View控制绘制在均分的几个锚点上
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
PointF anchorPoint = new PointF((childDis * i + defaultLeftRightGap + childSideLength / 2 + getPaddingLeft()), getPaddingTop() + childSideLength / 2);
anchorList.add(anchorPoint);
childView.layout((int) (anchorPoint.x - cWidth / 2), (int) (anchorPoint.y - cHeight / 2), (int) (anchorPoint.x + cWidth / 2), (int) (anchorPoint.y + cHeight / 2));
}
PointF pointF = anchorList.get(0);
bezierCircular.setCenter(pointF.x, pointF.y);
bezierCircular.initControlPoint();
}

绘制贝塞尔小球

将贝塞尔小球的一些参数及计算封装成一个对象BezierCircular,因为刚开始只是看了原博客的思路就动手了,绘制贝塞尔小球使用了最原始的方法,定义了4个数据点和8个控制点,在进行五个状态的绘制计算的时候太麻烦了,后面看了博客中的Demo,发现自己的计算太原始笨重了,博客中的demo中关于小球的绘制更加面向对象,更加简洁。不过既然是原创,还是要贴出自己的代码,仅供参考😄

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
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
public class BezierCircular {
private static final String TAG = "BezierCircular";

private static final float C = 0.551915024494f; //常量


//圆中心坐标
float centerX;
float centerY;

//圆半径
float radius;

private PointF currentPoint;
private PointF targetPoint;
private float mDifference;

private float stretchDistance;
private float cDistance;

private float moveDistance;

private float[] mData = new float[8]; //顺时针记录绘制圆形的四个数据点
private float[] mCtrl = new float[16]; //顺时针记录绘制圆形的八个控制点

public BezierCircular(float radius) {
this.radius = radius;
stretchDistance = radius / 3 * 2;
mDifference = radius * C;
cDistance = mDifference * 0.45f;
}

public void setCenter(float centerX, float centerY) {
this.centerX = centerX;
this.centerY = centerY;

}



public void initControlPoint() {

//初始化数据点
mData[0] = centerX;
mData[1] = centerY + radius;

mData[2] = centerX + radius;
mData[3] = centerY;

mData[4] = centerX;
mData[5] = centerY - radius;

mData[6] = centerX - radius;
mData[7] = centerY;

//初始化控制点
mCtrl[0] = mData[0] + mDifference;
mCtrl[1] = mData[1];

mCtrl[2] = mData[2];
mCtrl[3] = mData[3] + mDifference;

mCtrl[4] = mData[2];
mCtrl[5] = mData[3] - mDifference;

mCtrl[6] = mData[4] + mDifference;
mCtrl[7] = mData[5];

mCtrl[8] = mData[4] - mDifference;
mCtrl[9] = mData[5];

mCtrl[10] = mData[6];
mCtrl[11] = mData[7] - mDifference;

mCtrl[12] = mData[6];
mCtrl[13] = mData[7] + mDifference;

mCtrl[14] = mData[0] - mDifference;
mCtrl[15] = mData[1];
}


public void setCurrentAndTarget(PointF currentPoint, PointF targetPoint) {
this.currentPoint = currentPoint;
this.targetPoint = targetPoint;
float distance = targetPoint.x - currentPoint.x;
moveDistance = distance > 0 ? distance - 2 * stretchDistance : distance + 2 * stretchDistance;
}

public void setProgress(float progress) {
if ((progress > 0 && progress <= 0.2) || (progress < 0 && progress >= -0.2)) {
model1(progress);
} else if ((progress > 0.2 && progress <= 0.5) || (progress < -0.2 && progress >= -0.5)) {
model2(progress);
} else if ((progress > 0.5 && progress <= 0.8) || (progress < -0.5 && progress >= -0.8)) {
model3(progress);
} else if ((progress > 0.8 && progress <= 0.9) || (progress < -0.8 && progress >= -0.9)) {
model4(progress);
} else if ((progress > 0.9 && progress < 1) || (progress < -0.9 && progress > -1)) {
model5(progress);
}
// } else if (progress >= 1 || progress <= -1) {
// Log.i(TAG,"-------------------------------------------");
//// centerX = targetPoint.x;
//// centerY = targetPoint.y;
//// initControlPoint();
// }
}


public void model1(float progress) {
if (progress > 0)
mData[2] = centerX + radius + stretchDistance * progress * 5;

if (progress < 0)
mData[6] = centerX - radius + stretchDistance * progress * 5;

mCtrl[2] = mData[2];
if (progress > 0)
mCtrl[3] = mData[3] + mDifference + cDistance * progress * 5;

mCtrl[4] = mData[2];
if (progress > 0)
mCtrl[5] = mData[3] - mDifference - cDistance * progress * 5;

mCtrl[10] = mData[6];
if (progress < 0)
mCtrl[11] = mData[7] - mDifference + cDistance * progress * 5;

mCtrl[12] = mData[6];
if (progress < 0)
mCtrl[13] = mData[7] + mDifference - cDistance * progress * 5;
}

public void model2(float progress) {
model1(progress > 0 ? 0.2f : -0.2f);

progress = progress > 0 ? (progress - 0.2f) * (10f / 3) : (progress + 0.2f) * (10f / 3);
//初始化数据点
mData[0] = centerX + stretchDistance * progress;

if (progress > 0)
mData[2] = centerX + radius + stretchDistance * (1 + progress);
else
mData[2] = centerX + radius;


mData[4] = centerX + stretchDistance * progress;

if (progress < 0)
mData[6] = centerX - radius - stretchDistance + stretchDistance * progress;
else
mData[6] = centerX - radius;


//初始化控制点
mCtrl[0] = mData[0] + mDifference;

mCtrl[2] = mData[2];
if (progress > 0)
mCtrl[3] = mData[3] + mDifference + cDistance;
else
mCtrl[3] = mData[3] + mDifference - cDistance * progress;

mCtrl[4] = mData[2];
if (progress > 0)
mCtrl[5] = mData[3] - mDifference - cDistance;
else
mCtrl[5] = mData[3] - mDifference + cDistance * progress;


mCtrl[6] = mData[4] + mDifference;

mCtrl[8] = mData[4] - mDifference;

mCtrl[10] = mData[6];
if (progress > 0)
mCtrl[11] = mData[7] - mDifference - cDistance * progress;
else
mCtrl[11] = mData[7] - mDifference - cDistance;

mCtrl[12] = mData[6];
if (progress > 0)
mCtrl[13] = mData[7] + mDifference + cDistance * progress;
else
mCtrl[13] = mData[7] + mDifference + cDistance;


mCtrl[14] = mData[0] - mDifference;
}

public void model3(float progress) {
model2(progress > 0 ? 0.5f : -0.5f);
progress = progress > 0 ? (progress - 0.5f) * (10f / 3) : (progress + 0.5f) * (10f / 3);

//初始化数据点
if (progress > 0)
mData[0] = centerX + moveDistance * progress + stretchDistance;
else
mData[0] = centerX - moveDistance * progress - stretchDistance;


if (progress > 0)
mData[2] = centerX + moveDistance * progress + radius + 2 * stretchDistance;
else
mData[2] = centerX - moveDistance * progress + radius;


if (progress > 0)
mData[4] = centerX + moveDistance * progress + stretchDistance;
else
mData[4] = centerX - moveDistance * progress - stretchDistance;


if (progress > 0)
mData[6] = centerX + moveDistance * progress - radius;
else
mData[6] = centerX - moveDistance * progress - radius - 2 * stretchDistance;

//初始化控制点
mCtrl[0] = mData[0] + mDifference;

mCtrl[2] = mData[2];
mCtrl[3] = mData[3] + mDifference + cDistance;

mCtrl[4] = mData[2];
mCtrl[5] = mData[3] - mDifference - cDistance;

mCtrl[6] = mData[4] + mDifference;

mCtrl[8] = mData[4] - mDifference;

mCtrl[10] = mData[6];
mCtrl[11] = mData[7] - mDifference - cDistance;

mCtrl[12] = mData[6];
mCtrl[13] = mData[7] + mDifference + cDistance;

mCtrl[14] = mData[0] - mDifference;
}

public void model4(float progress) {


model3(progress > 0 ? 0.8f : -0.8f);

progress = progress > 0 ? (progress - 0.8f) * 10 : (progress + 0.8f) * 10;

//初始化数据点
if (progress > 0)
mData[0] = centerX + moveDistance + stretchDistance + stretchDistance * progress;
else
mData[0] = centerX + moveDistance - stretchDistance + stretchDistance * progress;

if (progress > 0)
mData[2] = centerX + moveDistance + radius + 2 * stretchDistance;
else
mData[2] = centerX + moveDistance + radius + stretchDistance * progress;

if (progress > 0)
mData[4] = centerX + moveDistance + stretchDistance + stretchDistance * progress;
else
mData[4] = centerX + moveDistance - stretchDistance + stretchDistance * progress;

if (progress > 0)
mData[6] = centerX + moveDistance - radius + stretchDistance * progress;
else
mData[6] = centerX + moveDistance - radius - 2 * stretchDistance;

//初始化控制点
mCtrl[0] = mData[0] + mDifference;

mCtrl[2] = mData[2];
if (progress > 0)
mCtrl[3] = mData[3] + mDifference + cDistance - cDistance * progress;
else
mCtrl[3] = mData[3] + mDifference + cDistance;

mCtrl[4] = mData[2];
if (progress > 0)
mCtrl[5] = mData[3] - mDifference - cDistance + cDistance * progress;
else
mCtrl[5] = mData[3] - mDifference - cDistance;

mCtrl[6] = mData[4] + mDifference;

mCtrl[8] = mData[4] - mDifference;

mCtrl[10] = mData[6];
if (progress > 0)
mCtrl[11] = mData[7] - mDifference - cDistance;
else
mCtrl[11] = mData[7] - mDifference - cDistance - cDistance * progress;

mCtrl[12] = mData[6];
if (progress > 0)
mCtrl[13] = mData[7] + mDifference + cDistance;
else
mCtrl[13] = mData[7] + mDifference + cDistance + cDistance * progress;

mCtrl[14] = mData[0] - mDifference;
}

public void model5(float progress) {
model4(progress > 0 ? 0.9f : -0.9f);

progress = progress > 0 ? (progress - 0.9f) * 10 : (progress + 0.9f) * 10;

//初始化数据点
if (progress > 0)
mData[0] = centerX + moveDistance + 2 * stretchDistance;
else
mData[0] = centerX + moveDistance - 2 * stretchDistance;

if (progress > 0)
mData[2] = centerX + moveDistance + radius + 2 * stretchDistance;
else
mData[2] = (float) (centerX + moveDistance + radius - stretchDistance - (Math.sin(Math.PI * 3 / 2 * Math.abs(progress) - Math.PI / 2) + 1) * stretchDistance);


if (progress > 0)
mData[4] = centerX + moveDistance + 2 * stretchDistance;
else
mData[4] = centerX + moveDistance - 2 * stretchDistance;

if (progress > 0)
mData[6] = (float) (centerX + moveDistance - radius + stretchDistance + (Math.sin(Math.PI * 3 / 2 * progress - Math.PI / 2) + 1) * stretchDistance);
else
mData[6] = centerX + moveDistance - radius - 2 * stretchDistance;


//初始化控制点
mCtrl[0] = mData[0] + mDifference;

mCtrl[2] = mData[2];
if (progress < 0)
mCtrl[3] = mData[3] + mDifference + cDistance + cDistance * progress;

mCtrl[4] = mData[2];
if (progress < 0)
mCtrl[5] = mData[3] - mDifference - cDistance - cDistance * progress;

mCtrl[6] = mData[4] + mDifference;

mCtrl[8] = mData[4] - mDifference;

mCtrl[10] = mData[6];
if (progress > 0)
mCtrl[11] = mData[7] - mDifference - cDistance + cDistance * progress;

mCtrl[12] = mData[6];
if (progress > 0)
mCtrl[13] = mData[7] + mDifference + cDistance - cDistance * progress;

mCtrl[14] = mData[0] - mDifference;
}



public void drawCircle(Canvas canvas, Paint mPaint) {
Path path = new Path();
path.moveTo(mData[0], mData[1]);

path.cubicTo(mCtrl[0], mCtrl[1], mCtrl[2], mCtrl[3], mData[2], mData[3]);
path.cubicTo(mCtrl[4], mCtrl[5], mCtrl[6], mCtrl[7], mData[4], mData[5]);
path.cubicTo(mCtrl[8], mCtrl[9], mCtrl[10], mCtrl[11], mData[6], mData[7]);
path.cubicTo(mCtrl[12], mCtrl[13], mCtrl[14], mCtrl[15], mData[0], mData[1]);

canvas.drawPath(path, mPaint);
}


public void resetCircular(PointF pointF) {
setCenter(pointF.x, pointF.y);
initControlPoint();
}

}

确定子View点击位置

通过OnTouchEvent 方法计算触摸点在哪个子View的绘制范围内,确定点击位置

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
    float touchX = 0;
float touchY = 0;

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchX = event.getX();
touchY = event.getY();
break;

case MotionEvent.ACTION_UP:
Log.i(TAG, "touchX: " + touchX + " touchY: " + touchY);
for (int i = 0; i < anchorList.size(); i++) {
PointF pointF = anchorList.get(i);
if (touchX > (pointF.x - childSideLength / 2) && touchX < (pointF.x + childSideLength / 2) && touchY > (pointF.y - childSideLength / 2) && touchY < (pointF.y + childSideLength / 2)) {
onClickIndex(i);
}
}
break;
}
return true;
}


private void onClickIndex(int position) {
if (!isAnimatorStart && !isViewPagerScoll && position != currentPosition) {
targetPosition = position;
isAnimatorStart = true;
startAnimator(); //开始动画
clickAnimator(); //点击效果
if (viewPager != null) {
viewPager.setCurrentItem(position);
}
// currentPosition = position;
Log.i(TAG, "点击了第 " + position + " 项!");
}
}

点击切换动画

通过ValueAnimator动态更改贝塞尔小球的绘制进度

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
/**
* 切换动画
*/
private void startAnimator() {
bezierCircular.setCurrentAndTarget(anchorList.get(currentPosition), anchorList.get(targetPosition));
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, targetPosition > currentPosition ? 1 : -1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
bezierCircular.setProgress((Float) animation.getAnimatedValue());
bezierPaint.setColor(circularColors.size() > 0 ? setCircularColor(Math.abs((Float) animation.getAnimatedValue())) : circularColor);
postInvalidate();
}
});

valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
currentPosition = targetPosition;
bezierPaint.setColor(circularColors.size() > 0 ? circularColors.get(currentPosition) : circularColor);
bezierCircular.resetCircular(anchorList.get(currentPosition));
isAnimatorStart = false;
postInvalidate();
super.onAnimationEnd(animation);
}
});

int count = Math.abs(targetPosition - currentPosition);
if (count == 0) {
return;
}
int duration = 600;
valueAnimator.setDuration(duration);
valueAnimator.start();
}

与ViewPager联动

与ViewPager的联动这一块挺头疼的,ViewPager滚动过程中设置滑动监听 void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) 回调方法中的 positionOffset 参数,在从左往右滑是0~1逐渐增大,但是最后又会突变到0。而且 void onPageSelected(int position)回调方法并不是在ViewPager滑动结束的时候调用,而是在你的手指离开时调用,有可能ViewPager还在惯性滑动的时候void onPageSelected(int position)方法已经调用了,所以也没办法通过这个回调来确定 currentPositontargetPosition

通过观察,ViewPager的滑动监听 void onPageScrollStateChanged(int state)回调方法中有三个状态

  1. state == 1 表示正在滑动
  2. state == 2 表示滑动结束
  3. state == 0 表示什么都没有做

这里的滑动指的是手指在屏幕上的滑动,而当ViewPager惯性滑动结束时 state == 0,所以最后决定在void onPageScrollStateChanged(int state)方法中进行相关处理。

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
 public void setViewPager(ViewPager viewPager) {
this.viewPager = viewPager;

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (anchorList != null && anchorList.size() > 0 && !isAnimatorStart) {
isViewPagerScoll = true;
updateDrop(position, positionOffset, positionOffsetPixels);
}
// 页面正在滚动时不断调用
Log.d(TAG, "onPageScrolled————>" + " position:" + position + " positionOffest:" + positionOffset + " positionOffsetPixels:" + positionOffsetPixels);
}

@Override
public void onPageSelected(int position) {
Log.e(TAG, "onPagerSelected————> position:" + position);
isSelected = true;
}

@Override
public void onPageScrollStateChanged(int state) {
if (state == 0 && isSelected && !isAnimatorStart) {
// Log.e(TAG, "onPageScrollStateChanged————> 设置状态:");
isSelected = false;
isViewPagerScoll = false;
bezierCircular.setProgress(direction ? 1.0f : -1.0f);
currentPosition = targetPosition;

// Log.i(TAG, "currentPosition::::" + currentPosition);
bezierPaint.setColor(circularColors.size() > 0 ? circularColors.get(currentPosition) : circularColor);
bezierCircular.resetCircular(anchorList.get(currentPosition));
postInvalidate();
}
Log.i(TAG, "onPageScrollStateChanged————> state:" + state);
}
});
}


float lastProgress = 0;
float currentProgress = 0;


//滑动ViewPager时更新指示器的动画
private void updateDrop(int position, float positionOffset, int positionOffsetPixels) {

if ((position + positionOffset) - currentPosition > 0) {
direction = true;
} else if ((position + positionOffset) - currentPosition < 0) {
direction = false;
}

//防止数组越界
if ((!direction && currentPosition - 1 < 0) || (direction && currentPosition + 1 > getChildCount() - 1)) {
return;
}

if (direction) targetPosition = currentPosition + 1;
else targetPosition = currentPosition - 1;

currentProgress = positionOffset;

// Log.e(TAG, "direction:::" + direction + " currentPosition:::" + currentPosition + " targetPosition:::" + targetPosition);
bezierCircular.setCurrentAndTarget(anchorList.get(currentPosition), anchorList.get(targetPosition));

if (currentProgress == 0 && lastProgress > 0.9) {
if (lastProgress > 0.9) {
currentProgress = 1;
}
if (lastProgress < 0.1) {
currentProgress = 0;
}
}

bezierCircular.setProgress(direction ? currentProgress : currentProgress - 1);
bezierPaint.setColor(circularColors.size() > 0 ? setCircularColor(direction ? currentProgress : 1 - currentProgress) : circularColor);
invalidate();
lastProgress = currentProgress;
}

onDraw(Canvas canvas)

onDraw方法中代码就很少了

1
2
3
4
5
6
7
8
@Override
protected void onDraw(Canvas canvas) {

drawChildBg(canvas);
bezierCircular.drawCircle(canvas, bezierPaint);
drawClick(canvas);
super.onDraw(canvas);
}

附上子View背景绘制,及点击效果绘制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//绘制子View的背景
private void drawChildBg(Canvas canvas) {
if (anchorList == null || anchorList.size() == 0) {
Log.i(TAG, "锚点位置为空");
return;
}

for (int i = 0; i < anchorList.size(); i++) {
PointF pointF = anchorList.get(i);
canvas.drawCircle(pointF.x, pointF.y, (childSideLength - 4) / 2, childBgPaint);
}
}

//绘制点击效果
private void drawClick(Canvas canvas) {
PointF pointF = anchorList.get(targetPosition);

canvas.drawCircle(pointF.x, pointF.y, clickRadius, clickPaint);
}

效果

最终效果如下,可能与原概念图有些差距,但也算小有成就吧😄

附上github地址:https://github.com/lichenming0516/BezierIndicator

效果图

小结

通过这两次自定义View的学习尝试,让自己对自定义View的绘制流程有了更深刻的了解,一些常见方法onMeasure()onLayout()onDraw()以及自定义属性的解析理解的更清晰一点。对于自定义View这座大山应该能算的上爬上半山腰了吧 😄

坚持原创技术分享,您的支持将鼓励我继续创作!