Android-贝塞尔曲线

来源:互联网 发布:强制锁定4g网络的软件 编辑:程序博客网 时间:2024/05/22 12:02

——前言
——什么是贝塞尔曲线
——贝塞尔曲线的分类
——贝塞尔曲线代码实现
——贝塞尔曲线的应用

 

前言:

 

从去年开始了解贝塞尔曲线之后,发现开发中,不管是Android/Ios平台,还是web前端等,都有贝塞尔曲线的应用,通过绘制贝塞尔曲线,可以帮助开发者实现很多效果,例如一段时间内很流行的粘合型的下拉刷新、又如天气曲线图,同时,以贝塞尔曲线为基础的贝塞尔工具是所有绘图软件的最常用最实用的工具。

 

什么是贝塞尔曲线:

贝塞尔曲线(Béziercurve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。主要结构:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。


 

贝塞尔曲线的分类:

 

 

一阶贝塞尔曲线(线段):

公式:

\

 

 

意义:由P0至P1的连续点,描述的一条线段


 

二阶贝塞尔曲线(抛物线)

公式:

\

 

原理:由P0至P1的连续点Q0,描述一条线段。
由P1至P2的连续点Q1,描述一条线段。
由Q0至Q1的连续点B(t),描述一条二次贝塞尔曲线。


 

三阶贝塞尔曲线:

 

\

 

当然还有四阶曲线、五阶曲线......

 

一篇帮助理解贝塞尔曲线的文章:http://www.html-js.com/article/1628

一个帮助绘制贝塞尔曲线的网址:http://bezier.method.ac/


 

贝塞尔曲线代码实现:

 

我们一般使用的是二阶贝塞尔曲线和三阶贝塞尔曲线,从动态图和公式我们可以看出,贝塞尔曲线主要由于三个部分控制:起点,终点,中间的辅助控制点。如何利用这三个点画出贝塞尔曲线,在android自带的Path类中自带了方法,可以帮助我们实现贝塞尔曲线:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * Add a quadratic bezier from the last point, approaching control point
 * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
 * this contour, the first point is automatically set to (0,0).
 *
 * @param x1 The x-coordinate of the control point on a quadratic curve
 * @param y1 The y-coordinate of the control point on a quadratic curve
 * @param x2 The x-coordinate of the end point on a quadratic curve
 * @param y2 The y-coordinate of the end point on a quadratic curve
 */
publicvoid quadTo(floatx1, floaty1, floatx2, floaty2) {
    isSimplePath = false;
    native_quadTo(mNativePath, x1, y1, x2, y2);
}


 

 

quadTo()方法从上一个点为起点开始绘制贝塞尔曲线,其中(x1,y1)为辅助控制点,(x2,y2)为终点。

PathmPath=newPath();
mPath.moveTo(x0,y0);
mPath.quadTo(x1,y1,x2,y2);

如调用以上代码,即绘制起点(x0,y0),终点(x2,y2),辅助控制点(x1,y1)的贝塞尔曲线。因此,通过不断改变这三个点的位置,我们可以绘制出各种各样的曲线



?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * Add a cubic bezier from the last point, approaching control points
 * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
 * made for this contour, the first point is automatically set to (0,0).
 *
 * @param x1 The x-coordinate of the 1st control point on a cubic curve
 * @param y1 The y-coordinate of the 1st control point on a cubic curve
 * @param x2 The x-coordinate of the 2nd control point on a cubic curve
 * @param y2 The y-coordinate of the 2nd control point on a cubic curve
 * @param x3 The x-coordinate of the end point on a cubic curve
 * @param y3 The y-coordinate of the end point on a cubic curve
 */
publicvoid cubicTo(floatx1, floaty1, floatx2, floaty2,
                    floatx3, floaty3) {
    isSimplePath = false;
    native_cubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
}

cubicTo()方法从上一个点为起点开始绘制三阶贝塞尔曲线,其中(x1,y1),(x2,y2)为辅助控制点,(x3,y3)为终点。

 

 

贝塞尔曲线的应用

 

(1)二阶贝塞尔曲线——波浪

要实现一个波浪不断涌动的效果,这种效果在很多手机应用中可见,例如手机电量,内存剩余等。我们需要绘制带有平滑自然效果的曲线,这时候就需要贝塞尔曲线来辅助了。

 

动态图:

\

 

原理图:

 

\

 

 

图中的矩阵即为视图的可见范围,也就是我们手机常见的区域。通过属性动画类ValueAnimator不断改变点1的横坐标,随着点1横坐标向右移动,点2,点3,点4,点5,以及四个控制点的坐标随着点1的移动同时位移相同距离,每一次坐标点更新,我们调用一次invalidate()方法,调用draw重新绘制视图,绘制四段贝塞尔曲线。最后点1移动到原先点3的位置,这样就完成了一次动画。

 

这样,通过循环不断的动画效果,我们就实现了波浪的效果。

 

onDraw() 代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protectedvoid onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if(!mIsRunning || !mHasInit)
        return;
    mPath.reset();
    mPath.moveTo(mLeft1.x, mLeft1.y);
    mPath.quadTo(mControlLeft1.x, mControlLeft1.y, mLeft2.x, mLeft2.y);
    mPath.quadTo(mControlLeft2.x, mControlLeft2.y, mFirst.x, mFirst.y);
    mPath.quadTo(mControlFirst.x, mControlFirst.y, mSecond.x, mSecond.y);
    mPath.quadTo(mControlSecond.x, mControlSecond.y, mRight.x, mRight.y);
    mPath.lineTo(mRight.x, mHeight);
    mPath.lineTo(mLeft1.x, mHeight);
    mPath.lineTo(mLeft1.x, mLeft1.y);
    canvas.drawPath(mPath, mPaint);
}

 

 

(2)二阶贝塞尔曲线——粘连体

利用二阶贝塞尔曲线还可以实现,类似两种物体粘合在一起的效果,比如我们常用的qq,在qq聊天列表上有一个非常有意思的功能,就是当我们用手指移动聊天列表上的未读消息标志的时候,它与聊天列表会产生粘连的效果:

\

现在,我们来模拟这种效果,利用学到的二阶贝塞尔曲线来绘制。

 

\

我们看到原理图,基本构造为两个圆,和两端贝塞尔曲线,绘制贝塞尔曲线,由于这是一个二阶的贝塞尔曲线,我们只需要一个控制点,在这个图里,我们的两条贝塞尔曲线的两个控制点分别为(x1,y1)(x4,y4)的中点,(x2,y2)(x3,y3)的中点。

从图中可以看出,我们的贝塞尔曲线由我们的控制点控制,控制点又是被起点和终点控制着,因此,当两个圆距离越大,曲线越趋于平缓,当两个圆距离越小,曲线的波动度越大,这样,我们想要的粘连的效果就实现了。另外,这里有一个还有角度(图中的45度角)可以用来控制,也可以作为控制曲线波动度的参数。

通过以上分析,我们通过一个方法来绘制两个圆之间的粘连体路径:

 

 

?
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
/**
 * 画粘连体
 * @param cx1     圆心x1
 * @param cy1     圆心y1
 * @param r1      圆半径r1
 * @param offset1 贝塞尔曲线偏移角度offset1
 * @param cx2     圆心x2
 * @param cy2     圆心y2
 * @param r2      圆半径r2
 * @param offset2 贝塞尔曲线偏移角度offset2
 * @return
 */
publicstatic Path drawAdhesionBody(floatcx1, floatcy1, floatr1, floatoffset1, float
        cx2,floatcy2, floatr2, floatoffset2) {
     
    /* 求三角函数 */
    floatdegrees =(float) Math.toDegrees(Math.atan(Math.abs(cy2 - cy1) / Math.abs(cx2 - cx1)));
     
    /* 根据圆1与圆2的相对位置求四个点 */
    floatdifferenceX = cx1 - cx2;
    floatdifferenceY = cy1 - cy2;
 
    /* 两条贝塞尔曲线的四个端点 */
    floatx1,y1,x2,y2,x3,y3,x4,y4;
     
    /* 圆1在圆2的下边 */
    if(differenceX == 0&& differenceY > 0) {
        x2 = cx2 - r2 * (float) Math.sin(Math.toRadians(offset2));
        y2 = cy2 + r2 * (float) Math.cos(Math.toRadians(offset2));
        x4 = cx2 + r2 * (float) Math.sin(Math.toRadians(offset2));
        y4 = cy2 + r2 * (float) Math.cos(Math.toRadians(offset2));
        x1 = cx1 - r1 * (float) Math.sin(Math.toRadians(offset1));
        y1 = cy1 - r1 * (float) Math.cos(Math.toRadians(offset1));
        x3 = cx1 + r1 * (float) Math.sin(Math.toRadians(offset1));
        y3 = cy1 - r1 * (float) Math.cos(Math.toRadians(offset1));
    }
    /* 圆1在圆2的上边 */
    elseif (differenceX == 0&& differenceY < 0) {
        x2 = cx2 - r2 * (float) Math.sin(Math.toRadians(offset2));
        y2 = cy2 - r2 * (float) Math.cos(Math.toRadians(offset2));
        x4 = cx2 + r2 * (float) Math.sin(Math.toRadians(offset2));
        y4 = cy2 - r2 * (float) Math.cos(Math.toRadians(offset2));
        x1 = cx1 - r1 * (float) Math.sin(Math.toRadians(offset1));
        y1 = cy1 + r1 * (float) Math.cos(Math.toRadians(offset1));
        x3 = cx1 + r1 * (float) Math.sin(Math.toRadians(offset1));
        y3 = cy1 + r1 * (float) Math.cos(Math.toRadians(offset1));
    }
    /* 圆1在圆2的右边 */
    elseif (differenceX > 0&& differenceY == 0) {
        x2 = cx2 + r2 * (float) Math.cos(Math.toRadians(offset2));
        y2 = cy2 + r2 * (float) Math.sin(Math.toRadians(offset2));
        x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(offset2));
        y4 = cy2 - r2 * (float) Math.sin(Math.toRadians(offset2));
        x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(offset1));
        y1 = cy1 + r1 * (float) Math.sin(Math.toRadians(offset1));
        x3 = cx1 - r1 * (float) Math.cos(Math.toRadians(offset1));
        y3 = cy1 - r1 * (float) Math.sin(Math.toRadians(offset1));
    }
    /* 圆1在圆2的左边 */
    elseif (differenceX < 0&& differenceY == 0) {
        x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(offset2));
        y2 = cy2 + r2 * (float) Math.sin(Math.toRadians(offset2));
        x4 = cx2 - r2 * (float) Math.cos(Math.toRadians(offset2));
        y4 = cy2 - r2 * (float) Math.sin(Math.toRadians(offset2));
        x1 = cx1 + r1 * (float) Math.cos(Math.toRadians(offset1));
        y1 = cy1 + r1 * (float) Math.sin(Math.toRadians(offset1));
        x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(offset1));
        y3 = cy1 - r1 * (float) Math.sin(Math.toRadians(offset1));
    }
    /* 圆1在圆2的右下角 */
    elseif (differenceX > 0&& differenceY > 0) {
        x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(180- offset2 - degrees));
        y2 = cy2 + r2 * (float) Math.sin(Math.toRadians(180- offset2 - degrees));
        x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(degrees - offset2));
        y4 = cy2 + r2 * (float) Math.sin(Math.toRadians(degrees - offset2));
        x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(degrees - offset1));
        y1 = cy1 - r1 * (float) Math.sin(Math.toRadians(degrees - offset1));
        x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(180- offset1 - degrees));
        y3 = cy1 - r1 * (float) Math.sin(Math.toRadians(180- offset1 - degrees));
    }
    /* 圆1在圆2的左上角 */
    elseif (differenceX < 0&& differenceY < 0) {
        x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(degrees - offset2));
        y2 = cy2 - r2 * (float) Math.sin(Math.toRadians(degrees - offset2));
        x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(180- offset2 - degrees));
        y4 = cy2 - r2 * (float) Math.sin(Math.toRadians(180- offset2 - degrees));
        x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(180- offset1 - degrees));
        y1 = cy1 + r1 * (float) Math.sin(Math.toRadians(180- offset1 - degrees));
        x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(degrees - offset1));
        y3 = cy1 + r1 * (float) Math.sin(Math.toRadians(degrees - offset1));
    }
    /* 圆1在圆2的左下角 */
    elseif (differenceX < 0&& differenceY > 0) {
        x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(degrees - offset2));
        y2 = cy2 + r2 * (float) Math.sin(Math.toRadians(degrees - offset2));
        x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(180- offset2 - degrees));
        y4 = cy2 + r2 * (float) Math.sin(Math.toRadians(180- offset2 - degrees));
        x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(180- offset1 - degrees));
        y1 = cy1 - r1 * (float) Math.sin(Math.toRadians(180- offset1 - degrees));
        x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(degrees - offset1));
        y3 = cy1 - r1 * (float) Math.sin(Math.toRadians(degrees - offset1));
    }
    /* 圆1在圆2的右上角 */
    else{
        x2 = cx2 - r2 * (float) Math.cos(Math.toRadians(180- offset2 - degrees));
        y2 = cy2 - r2 * (float) Math.sin(Math.toRadians(180- offset2 - degrees));
        x4 = cx2 + r2 * (float) Math.cos(Math.toRadians(degrees - offset2));
        y4 = cy2 - r2 * (float) Math.sin(Math.toRadians(degrees - offset2));
        x1 = cx1 - r1 * (float) Math.cos(Math.toRadians(degrees - offset1));
        y1 = cy1 + r1* (float) Math.sin(Math.toRadians(degrees - offset1));
        x3 = cx1 + r1 * (float) Math.cos(Math.toRadians(180- offset1 - degrees));
        y3 = cy1 + r1 * (float) Math.sin(Math.toRadians(180- offset1 - degrees));
    }
     
    /* 贝塞尔曲线的控制点 */
    floatanchorX1,anchorY1,anchorX2,anchorY2;
     
    /* 圆1大于圆2 */
    if(r1 > r2) {
        anchorX1 = (x2 + x3) / 2;
        anchorY1 = (y2 + y3) / 2;
        anchorX2 = (x1 + x4) / 2;
        anchorY2 = (y1 + y4) / 2;
    }
    /* 圆1小于或等于圆2 */
    else{
        anchorX1 = (x1 + x4) / 2;
        anchorY1 = (y1 + y4) / 2;
        anchorX2 = (x2 + x3) / 2;
        anchorY2 = (y2 + y3) / 2;
    }
     
    /* 画粘连体 */
    Path path = newPath();
    path.reset();
    path.moveTo(x1, y1);
    path.quadTo(anchorX1, anchorY1, x2, y2);
    path.lineTo(x4, y4);
    path.quadTo(anchorX2, anchorY2, x3, y3);
    path.lineTo(x1, y1);
    returnpath;
}

 

 

再来看仿QQ聊天列表的粘连效果,我们已经实现了粘连体的绘制,接下来,我们需要实现以上的基本效果,我们给控件设置一个粘连的最大距离,即如果两个圆之间的距离超过这个值,则不再绘制粘连体。

好了,我们看效果图:

 

 

粘连体除了在类似QQ上这种效果,其实还可以做很多事,比如,如果我们用它来实现一个加载页面的效果呢。

 

\

 

\

 

 

(3)三阶贝塞尔曲线——弹性球

三阶贝塞尔曲线,就是有两个控制点,公式太复杂,我也不是很理解,不过通过之前给出的那个网址来理解,还是比较好明白的,它相比二阶曲线的优点是,由于控制点的增加,它能够更加轻松地绘制出更平滑更自然的曲线。

先来看一个web前端的效果:

\

 

真的是很酷炫......

如何绘制类似这种,看起来具有弹性球的滑动球,我们需要使用三阶贝塞尔曲线,那么首先如何用三阶贝塞尔曲线绘制出一个圆,这里有一篇文章,是关于如何用贝塞尔曲线绘制圆:http://spencermortensen.com/articles/bezier-circle/,大概意思是讲,我们需要一个值就是c=0.552284749,如下图,要绘制右上角的圆弧,我们需要两个控制点,其中B就是一个控制点,我们需要保证AB=c*r,即可以画出1/4的圆弧,以此类推,连续画四段这样的圆弧,就可以画出一个标准的圆。

 

\

 

 

接下来我们观察弹性球的运动,大概可以分为以下几个阶段:

 

1)开始启动,此时右边点位移,其他点不动

 

\

 

2)开始加速

\

 

 

3)减速

\

 

 

4)到达终点

\

 

 

5)回弹效果

\

 

 

弹性球代码:

?
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
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
 packagecom.zero.bezier.widget.elastic;
 
importandroid.animation.Animator;
importandroid.animation.ValueAnimator;
importandroid.graphics.Path;
importandroid.graphics.PointF;
importandroid.view.animation.AccelerateDecelerateInterpolator;
 
/**
 * 弹性球
 * @author linzewu
 * @date 2016/6/1
 */
publicclass ElasticBall extendsBall {
    /**
     * 向上运动
     */
    privatestatic final int DIRECTION_UP = 1;
    /**
     * 向下运动
     */
    privatestatic final int DIRECTION_DOWN = 2;
    /**
     * 向左运动
     */
    privatestatic final int DIRECTION_LEFT = 3;
    /**
     * 向右运动
     */
    privatestatic final int DIRECTION_RIGHT = 4;
    /**
     * 运动方向
     */
    privateint mDirection;
    /**
     * 动画完成百分比(0~1)
     */
    privatefloat mAnimPercent;
    /**
     * 弹性距离
     */
    privatefloat mElasticDistance;
    /**
     * 弹性比例
     */
    privatefloat mElasticPercent = 0.8f;
    /**
     * 位移距离
     */
    privatefloat mMoveDistance;
    /**
     * 动画消费时间
     */
    privatelong mDuration = 1500;
     
    /**
     * 偏移值
     */
    privatefloat offsetTop, offsetBottom, offsetLeft, offsetRight;
    /**
     * 圆形偏移比例
     */
    privatefloat c = 0.551915024494f;
     
    privatefloat c2 = 0.65f;
    /**
     * 动画开始点
     */
    privateBall mStartPoint;
 
    /**
     * 动画结束点
     */
    privateBall mEndPoint;
     
    /**
     * 构造方法
     *
     * @param x 圆心横坐标
     * @param y 圆心纵坐标
     * @param radius 圆半径
     */
    publicElasticBall(floatx, floaty, floatradius) {
        super(x, y, radius);
        init();
    }
     
     
    privatevoid init() {
        mElasticDistance = mElasticPercent * radius;
        offsetTop = c * radius;
        offsetBottom = c * radius;
        offsetLeft = c * radius;
        offsetRight = c * radius;
    }
     
    publicinterface ElasticBallInterface{
        voidonChange(Path path);
        voidonFinish();
    }
 
    privateElasticBallInterface mElasticBallInterface;
 
    /**
     * 对外公布方法,设置弹性比例 (0~1)
     * @param elasticPercent
     */
    publicvoid setElasticPercent(floatelasticPercent) {
         
    }
    /**
     * 对外公布方法,设置动画时间
     * @param duration
     */
    publicvoid setDuration(longduration) {
        this.mDuration = duration;
    }
     
    /**
     * 对外公布方法, 开启动画
     * @param endPoint
     */
    publicvoid startElasticAnim(PointF endPoint, ElasticBallInterface elasticBallInterface) {
        this.mEndPoint = newElasticBall(endPoint.x, endPoint.y, radius);
        this.mStartPoint = newElasticBall(x, y, radius);
        this.mStatusPoint1 = newElasticBall(x, y, radius);
        this.mStatusPoint2 = newElasticBall(x, y, radius);
        this.mStatusPoint3 = newElasticBall(x, y, radius);
        this.mStatusPoint4 = newElasticBall(x, y, radius);
        this.mStatusPoint5 = newElasticBall(x, y, radius);
        this.mElasticBallInterface = elasticBallInterface;
        judgeDirection();
        mMoveDistance = getDistance(mStartPoint.x, mStatusPoint1.y, endPoint.x, endPoint.y);
        animStatus0();
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
        valueAnimator.setDuration(mDuration);
        valueAnimator.setInterpolator(newAccelerateDecelerateInterpolator());
        valueAnimator.start();
        valueAnimator.addUpdateListener(newValueAnimator.AnimatorUpdateListener() {
            @Override
            publicvoid onAnimationUpdate(ValueAnimator animation) {
                mAnimPercent = (float) animation.getAnimatedValue();
                if(mAnimPercent>=0&& mAnimPercent <= 0.2){
                    animStatus1();
                }
                elseif(mAnimPercent > 0.2&& mAnimPercent <= 0.5){
                    animStatus2();
                }
                elseif(mAnimPercent > 0.5&& mAnimPercent <= 0.8){
                    animStatus3();
                }
                elseif(mAnimPercent > 0.8&& mAnimPercent <= 0.9){
                    animStatus4();
                }
                elseif(mAnimPercent > 0.9&&mAnimPercent <= 1){
                    animStatus5();
                }
                if(mElasticBallInterface != null) {
                    mElasticBallInterface.onChange(drawElasticCircle(topX, topY, offsetTop, offsetTop,
                            bottomX, bottomY, offsetBottom, offsetBottom,
                            leftX, leftY, offsetLeft, offsetLeft,
                            rightX, rightY, offsetRight, offsetRight));
                }
            }
        });
        valueAnimator.addListener(newAnimator.AnimatorListener() {
            @Override
            publicvoid onAnimationStart(Animator animation) {
                 
            }
 
            @Override
            publicvoid onAnimationEnd(Animator animation) {
                if(mElasticBallInterface != null) {
                    mElasticBallInterface.onFinish();
                }
            }
 
            @Override
            publicvoid onAnimationCancel(Animator animation) {
 
            }
 
            @Override
            publicvoid onAnimationRepeat(Animator animation) {
 
            }
        });
    }
 
    privatevoid judgeDirection() {
        if(mEndPoint.x - mStartPoint.x > 0) {
            mDirection = DIRECTION_RIGHT;
        }elseif (mEndPoint.x - mStartPoint.x < 0) {
            mDirection = DIRECTION_LEFT;
        }elseif (mEndPoint.y - mStartPoint.x > 0) {
            mDirection = DIRECTION_DOWN;
        }elseif (mEndPoint.y - mStartPoint.y < 0){
            mDirection = DIRECTION_UP;
        }
    }
     
    /**
     * 动画状态0 (初始状态:圆形)
     */
    privatevoid animStatus0() {
        offsetTop = c * radius;
        offsetBottom = c * radius;
        offsetLeft = c * radius;
        offsetRight = c * radius;
    }
     
    privateBall mStatusPoint1;
     
    /**
     * 动画状态1 (0~0.2)
     */
    privatevoid animStatus1() {
        floatpercent = mAnimPercent * 5f;
        if(mDirection == DIRECTION_LEFT) {
            leftX = mStartPoint.leftX - percent * mElasticDistance;
        }elseif (mDirection == DIRECTION_RIGHT) {
            rightX = mStartPoint.rightX + percent * mElasticDistance;
        }elseif (mDirection == DIRECTION_UP) {
            topY = mStartPoint.topY - percent * mElasticDistance;
        }elseif (mDirection == DIRECTION_DOWN) {
            bottomY = mStartPoint.bottomY + percent * mElasticDistance;
        }
        mStatusPoint1.refresh(x, y, topX, topY, bottomX, bottomY,
                leftX, leftY, rightX, rightY);
    }
 
    privateBall mStatusPoint2;
     
    /**
     * 动画状态2 (0.2~0.5)
     */
    privatevoid animStatus2() {
        floatpercent = (float) ((mAnimPercent - 0.2) * (10f / 3));
        if(mDirection == DIRECTION_LEFT) {
            leftX = mStatusPoint1.leftX - percent * (mMoveDistance / 2- mElasticDistance / 2);
            x = mStatusPoint1.x - percent * (mMoveDistance / 2);
            rightX = mStatusPoint1.rightX - percent * (mMoveDistance / 2- mElasticDistance / 2);
            topX = x;
            bottomX = x;
            //偏移值稍作变化
            offsetTop = radius * c + radius * ( c2 - c ) * percent;
            offsetBottom = radius * c + radius * ( c2 - c ) * percent;
        }elseif (mDirection == DIRECTION_RIGHT) {
            rightX = mStatusPoint1.rightX + percent * (mMoveDistance / 2- mElasticDistance / 2);
            x = mStatusPoint1.x + percent * (mMoveDistance / 2);
            leftX = mStatusPoint1.leftX + percent * (mMoveDistance / 2- mElasticDistance / 2);
            topX = x;
            bottomX = x;
            //偏移值稍作变化
            offsetTop = radius * c + radius * ( c2 - c ) * percent;
            offsetBottom = radius * c + radius * ( c2 - c ) * percent;
        }elseif (mDirection == DIRECTION_UP) {
            topY = mStatusPoint1.topY - percent * (mMoveDistance / 2- mElasticDistance / 2);
            y = mStatusPoint1.y - percent * (mMoveDistance / 2);
            bottomY = mStatusPoint1.bottomY - percent * (mMoveDistance / 2- mElasticDistance / 2);
            leftY = y;
            rightY = y;
            //偏移值稍作变化
            offsetLeft = radius * c + radius * ( c2 - c ) * percent;
            offsetRight = radius * c + radius * ( c2 - c ) * percent;
        }elseif (mDirection == DIRECTION_DOWN) {
            bottomY = mStatusPoint1.bottomY + percent * (mMoveDistance / 2- mElasticDistance / 2);
            y = mStatusPoint1.y + percent * (mMoveDistance / 2);
            topY = mStatusPoint1.topY + percent * (mMoveDistance / 2- mElasticDistance / 2);
            leftY = y;
            rightY = y;
            //偏移值稍作变化
            offsetLeft = radius * c + radius * ( c2 - c ) * percent;
            offsetRight = radius * c + radius * ( c2 - c ) * percent;
        }
        mStatusPoint2.refresh(x, y, topX, topY, bottomX, bottomY,
                leftX, leftY, rightX, rightY);
    }
 
    privateBall mStatusPoint3;
     
    /**
     * 动画状态3 (0.5~0.8)
     */
    privatevoid animStatus3() {
        floatpercent = (mAnimPercent - 0.5f) * (10f / 3f);
        if(mDirection == DIRECTION_LEFT) {
            leftX = mStatusPoint2.leftX - Math.abs(percent * (mEndPoint.rightX - mStatusPoint2
                    .rightX));
            x = mStatusPoint2.x - Math.abs(percent * (mEndPoint.x - mStatusPoint2.x));
            rightX = mStatusPoint2.rightX - Math.abs(percent * (mEndPoint.x - mStatusPoint2.x));
            topX = x;
            bottomX = x;
            //偏移值稍作变化
            offsetTop = radius * c2 - radius * ( c2 - c ) * percent;
            offsetBottom = radius * c2 - radius * ( c2 - c ) * percent;
        }elseif (mDirection == DIRECTION_RIGHT) {
            rightX = mStatusPoint2.rightX + percent * (mEndPoint.rightX - mStatusPoint2.rightX);
            x = mStatusPoint2.x + percent * (mEndPoint.x - mStatusPoint2.x);
            leftX = mStatusPoint2.leftX + percent * (mEndPoint.x - mStatusPoint2.x);
            topX = x;
            bottomX = x;
            //偏移值稍作变化
            offsetTop = radius * c2 - radius * ( c2 - c ) * percent;
            offsetBottom = radius * c2 - radius * ( c2 - c ) * percent;
        }elseif (mDirection == DIRECTION_UP) {
            topY = mStatusPoint2.topY - Math.abs(percent * (mEndPoint.topY - mStatusPoint2
                    .topY));
            y = mStatusPoint2.y - Math.abs(percent * (mEndPoint.y - mStatusPoint2.y));
            bottomY = mStatusPoint2.bottomY - Math.abs(percent * (mEndPoint.y - mStatusPoint2.y));
            leftY = y;
            rightY = y;
            //偏移值稍作变化
            offsetLeft = radius * c2 - radius * ( c2 - c ) * percent;
            offsetRight = radius * c2 - radius * ( c2 - c ) * percent;
        }elseif (mDirection == DIRECTION_DOWN) {
            bottomY = mStatusPoint2.bottomY + percent * (mEndPoint.bottomY - mStatusPoint2
                    .bottomY);
            y = mStatusPoint2.y + percent * (mEndPoint.y - mStatusPoint2.y);
            topY = mStatusPoint2.topY + percent * (mEndPoint.y - mStatusPoint2.y);
            leftY = y;
            rightY = y;
            //偏移值稍作变化
            offsetLeft = radius * c2 - radius * ( c2 - c ) * percent;
            offsetRight = radius * c2 - radius * ( c2 - c ) * percent;
        }
        mStatusPoint3.refresh(x, y, topX, topY, bottomX, bottomY,
                leftX, leftY, rightX, rightY);
    }
 
    privateBall mStatusPoint4;
     
    /**
     * 动画状态4 (0.8~0.9)
     */
    privatevoid animStatus4() {
        floatpercent = (float) (mAnimPercent - 0.8) * 10;
        if(mDirection == DIRECTION_LEFT) {
            rightX = mStatusPoint3.rightX - percent * (Math.abs(mEndPoint.rightX - mStatusPoint3
                    .rightX) + mElasticDistance/2);
            //再做一次赋值,防止和终点不重合
            leftX = mEndPoint.leftX;
            x = mEndPoint.x;
            bottomX = mEndPoint.bottomX;
            topX = mEndPoint.topX;
        }elseif (mDirection == DIRECTION_RIGHT) {
            leftX = mStatusPoint3.leftX + percent * (mEndPoint.leftX - mStatusPoint3.leftX +
                    mElasticDistance/2);
            //再做一次赋值,防止和终点不重合
            rightX = mEndPoint.rightX;
            x = mEndPoint.x;
            bottomX = mEndPoint.bottomX;
            topX = mEndPoint.topX;
        }elseif (mDirection == DIRECTION_UP) {
            bottomY = mStatusPoint3.bottomY - percent * (Math.abs(mEndPoint.bottomY - mStatusPoint3
                    .bottomY) + mElasticDistance/2);
            //再做一次赋值,防止和终点不重合
            topY = mEndPoint.topY;
            y = mEndPoint.y;
            leftY = mEndPoint.leftY;
            rightY = mEndPoint.rightY;
        }elseif (mDirection == DIRECTION_DOWN) {
            topY = mStatusPoint3.topY + percent * (mEndPoint.topY - mStatusPoint3
                    .topY + mElasticDistance/2);
            //再做一次赋值,防止和终点不重合
            bottomY = mEndPoint.bottomY;
            y = mEndPoint.y;
            leftY = mEndPoint.leftY;
            rightY = mEndPoint.rightY;
        }
        mStatusPoint4.refresh(x, y, topX, topY, bottomX, bottomY,
                leftX, leftY, rightX, rightY);
    }
 
    privateBall mStatusPoint5;
     
    /**
     * 动画状态5 (0.9~1)回弹
     */
    privatevoid animStatus5() {
        floatpercent = (float) (mAnimPercent - 0.9) * 10;
        if(mDirection == DIRECTION_LEFT) {
            rightX = mStatusPoint4.rightX + percent * (mEndPoint.rightX - mStatusPoint4.rightX);
        }elseif (mDirection == DIRECTION_RIGHT) {
            leftX = mStatusPoint4.leftX + percent * (mEndPoint.leftX - mStatusPoint4.leftX);
        }elseif (mDirection == DIRECTION_UP) {
            bottomY = mStatusPoint4.bottomY + percent * (mEndPoint.bottomY - mStatusPoint4.bottomY);
        }elseif (mDirection == DIRECTION_DOWN) {
            topY = mStatusPoint4.topY + percent * (mEndPoint.topY - mStatusPoint4.topY);
        }
        mStatusPoint5.refresh(x, y, topX, topY, bottomX, bottomY,
                leftX, leftY, rightX, rightY);
    }
 
    /**
     * 绘制弹性圆
     * 通过绘制四段三阶贝塞尔曲线,来实现有弹性变化的圆
     * @param topX
     * @param topY
     * @param offsetTop1
     * @param offsetTop2
     * @param bottomX
     * @param bottomY
     * @param offsetBottom1
     * @param offsetBottom2
     * @param leftX
     * @param leftY
     * @param offsetLeft1
     * @param offsetLeft2
     * @param rightX
     * @param rightY
     * @param offsetRight1
     * @param offsetRight2
     * @return
     */
    privatePath drawElasticCircle(
            floattopX, floattopY, floatoffsetTop1, floatoffsetTop2,
            floatbottomX, floatbottomY, floatoffsetBottom1, floatoffsetBottom2,
            floatleftX, floatleftY, floatoffsetLeft1, floatoffsetLeft2,
            floatrightX, floatrightY, floatoffsetRight1, floatoffsetRight2
    ) {
        /**
         * 绘制每一段三阶贝塞尔曲线需要两个控制点
         */
        PointF controlTop1, controlTop2, controlBottom1, controlBottom2,
                controlLeft1, controlLeft2, controlRight1, controlRight2;
        controlTop1 = newPointF();
        controlTop1.x = topX - offsetTop1;
        controlTop1.y = topY;
        controlTop2 = newPointF();
        controlTop2.x = topX + offsetTop2;
        controlTop2.y = topY;
        controlBottom1 = newPointF();
        controlBottom1.x = bottomX - offsetBottom1;
        controlBottom1.y = bottomY;
        controlBottom2 = newPointF();
        controlBottom2.x = bottomX + offsetBottom2;
        controlBottom2.y = bottomY;
        controlLeft1 = newPointF();
        controlLeft1.x = leftX;
        controlLeft1.y = leftY - offsetLeft1;
        controlLeft2 = newPointF();
        controlLeft2.x = leftX;
        controlLeft2.y = leftY + offsetLeft2;
        controlRight1 = newPointF();
        controlRight1.x = rightX;
        controlRight1.y = rightY - offsetRight1;
        controlRight2 = newPointF();
        controlRight2.x = rightX;
        controlRight2.y = rightY + offsetRight2;
 
        Path path = newPath();
        /**
         * 绘制top到left的圆弧
         */
        path.moveTo(topX, topY);
        path.cubicTo(controlTop1.x, controlTop1.y, controlLeft1.x, controlLeft1.y, leftX, leftY);
        /**
         * 绘制left到bottom的圆弧
         */
        path.cubicTo(controlLeft2.x ,controlLeft2.y, controlBottom1.x, controlBottom1.y, bottomX,
                bottomY);
        /**
         * 绘制bottom到right的圆弧
         */
        path.cubicTo(controlBottom2.x, controlBottom2.y, controlRight2.x, controlRight2.y,
                rightX, rightY);
        /**
         * 绘制right到top的圆弧
         */
        path.cubicTo(controlRight1.x, controlRight1.y, controlTop2.x, controlTop2.y, topX, topY);
        returnpath;
    }
 
    /**
     * 求两点之间的距离
     * @param x1 第一个点的横坐标
     * @param y1 第一个点的纵坐标
     * @param x2 第二个点的横坐标
     * @param y2 第二个点的纵坐标
     * @return 两点距离
     */
    privatefloat getDistance(floatx1, floaty1, floatx2, floaty2) {
        return(float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }
     
}

上面完成了一个弹性球的封装,可以实现四个方向的运动,然后我们实现一个弹性球的loader:

 

\

 

 

贝塞尔曲线还有很多应用的地方,或者说在各个领域都有。

去年开始在黄同学的影响下,慢慢地去实现一些利用贝塞尔曲线实现的效果,源码有相当一部分代码也是来自于黄同学,也得益于网络上大多数技术博客无私的分享,希望自己能够通过学习这样一个开发的绘图曲线,有所提高。

0 0