贝塞尔曲线

概述

贝塞尔曲线的运用是十分广泛的,可以说贝塞尔曲线奠定了计算机绘图的基础(因为它可以将任何复杂的图形用精确的数学语言进行描述),在你不经意间就已经使用过它了。

贝塞尔曲线作用十分广泛,简单举几个的例子:

  • QQ小红点拖拽效果
  • 一些炫酷的下拉刷新控件
  • 阅读软件的翻书效果
  • 一些平滑的折线图的制作
  • 很多炫酷的动画效果
了解贝塞尔曲线的原理

贝塞尔曲线是用一系列点来控制曲线状态的,这些点简单分为两类:

一阶曲线原理:

一阶曲线是没有控制点的,仅有两个数据点(A 和 B),最终效果一个线段。

上图表示的是一阶曲线生成过程中的某一个阶段,动态过程可以参照下图(本文中贝塞尔曲线相关的动态演示图片来自维基百科)。

一阶曲线其实就是前面讲解过的lineTo

公式:B(t)就是运动点在t时刻的坐标,p0起点,p1终点

二阶曲线原理:

二阶曲线由两个数据点(A 和 C),一个控制点(B)来描述曲线状态,大致如下:

上图中红色曲线部分就是传说中的二阶贝塞尔曲线,动图生成过程:

二阶对应的方法就是quadTo()

公式:起点P0終点P2,控制点就是P1,运动点在P0,P1,P2三个点的约束下,运动形成的轨迹就是红色的曲线

三阶曲线原理:

三阶曲线由两个数据点(A 和 D),两个控制点(B 和 C)来描述曲线状态,如下:

动态描述:

三阶曲线对应的方法是cubicTo

公式:

实战:做一个水位上升的动画

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
class BezierView :View {

val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
val mPath = Path()
val paint = Paint(Paint.ANTI_ALIAS_FLAG)

var viewWidth = 0f //控件的宽高
var viewHeight = 0f
var commandX = 0f //控制点的坐标
var commandY = 0f
var waterHeight = 0f //水位高度
var isInc = true //判断控制点是该右移还是左移


constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?) : super(context)

//初始化画笔 路径
init {
//画笔
mPaint.color = Color.parseColor("#AFDEE4")

//辅助画笔
paint.color = Color.RED
paint.strokeWidth = 5f
}

//获取控件的宽和高
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = w.toFloat()
viewHeight = h.toFloat()
//控制点 开始时的y坐标
commandY = 7/8f * viewHeight
//终点一开始的y坐标,也是就水位水平高度 红色辅助线
waterHeight = 15/16f * viewHeight
}

//绘制
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//起始点位置
mPath.moveTo(-1/4f*viewWidth,waterHeight)
//绘制水波浪
mPath.quadTo(commandX,commandY,viewWidth + 1/4f*viewWidth,waterHeight)
//绘制波浪下方闭合区域
mPath.lineTo(viewWidth+1/4f*viewWidth,viewHeight) //这里的绘制过程画图看看就明白了,画完波浪往下画一条线,再往左画一条线,最后闭合
mPath.lineTo(-1/4f*viewWidth,viewHeight)
mPath.close()
//绘制路径
canvas.drawPath(mPath,mPaint)
//绘制红色水位高度辅助线
canvas.drawLine(0f,waterHeight,viewWidth,waterHeight,paint)

//产生波浪左右涌动的感觉
if (commandX >= viewWidth + 1 / 4F * viewWidth) {//控制点坐标大于等于终点坐标改标识
isInc = false;
} else if (commandX <= -1 / 4F * viewWidth) {//控制点坐标小于等于起点坐标改标识
isInc = true;
}
commandX = if (isInc) commandX + 20 else commandX - 20
//水位不断加高 当距离控件顶端还有1/8的高度时,不再上升
if (commandY >= 1 / 8f * viewHeight) {
commandY -= 2;
waterHeight -= 2;
}
//路径重置
mPath.reset();
// 重绘
invalidate();
}

//测量
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val wSpecMode = MeasureSpec.getMode(widthMeasureSpec)
val wSpecSize = MeasureSpec.getSize(widthMeasureSpec)
val hSpecMode = MeasureSpec.getMode(heightMeasureSpec)
val hSpecSize = MeasureSpec.getSize(heightMeasureSpec)

if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(300, 300)
} else if (wSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(300, hSpecSize)
} else if (hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(wSpecSize, 300)
}
}
}
0%