1.贝塞尔曲线介绍
贝塞尔曲线的数学基础是早在 1912 年就广为人知的伯恩斯坦多项式。但直到 1959 年,当时就职于雪铁龙的法国数学家 Paul de Casteljau 才开始对它进行图形化应用的尝试,并提出了一种数值稳定的 de Casteljau 算法。然而贝塞尔曲线的得名,却是由于 1962 年另一位就职于雷诺的法国工程师 Pierre Bézier 的广泛宣传。他使用这种只需要很少的控制点就能够生成复杂平滑曲线的方法,来辅助汽车车体的工业设计。正是因为控制简便却具有极强的描述能力,贝塞尔曲线在工业设计领域迅速得到了广泛的应用。不仅如此,在计算机图形学领域,尤其是矢量图形学,贝塞尔曲线也占有重要的地位。
您或许不知道在生活中已经用过贝塞尔曲线,我举个例子您就恍然大悟,Photoshop中的钢笔工具就是用了贝塞尔曲线的原理.在android中,我们利用贝塞尔曲线能做出很炫酷的效果,如下图:
是不是很好看,下一章我们就做这么一个效果来练习一下!
2.贝塞尔曲线原理
接下来我们介绍一下贝塞尔曲线的原理:
2.1 贝塞尔二阶曲线
1.在平面内任选 3 个不共线的点,依次用线段连接。如下图:
2.在第一条线段上任选一个点 D。计算该点到线段起点的距离 AD,与该线段总长 AB 的比例。如下图:
3.根据上一步得到的比例,从第二条线段上找出对应的点 E,使得 AD:AB = BE:BC。如下图:
4.连接这两点 DE。如下图:
5.从新的线段 DE 上再次找出相同比例的点 F,使得 DF:DE = AD:AB = BE:BC。如下图:
到这里,我们就确定了贝塞尔曲线上的一个点 F。接下来,请稍微回想一下中学所学的极限知识,让选取的点 D 在第一条线段上从起点 A 移动到终点 B,找出所有的贝塞尔曲线上的点 F。所有的点找出来之后,我们也得到了这条贝塞尔曲线。如下图:
6.如果你实在想象不出这个过程,没关系,看动画!
回过头来看这条贝塞尔曲线,为了确定曲线上的一个点,需要进行两轮取点的操作,因此我们称得到的贝塞尔曲线为二次曲线。计算公式如下:
2.2 贝塞尔三阶曲线
当控制点个数为 4 时,情况是怎样的?
步骤都是相同的,只不过我们每确定一个贝塞尔曲线上的点,要进行三轮取点操作。如图,AE:AB = BF:BC = CG:CD = EH:EF = FI:FG = HJ:HI,其中点 J 就是最终得到的贝塞尔曲线上的一个点。
这样我们得到的是一条三次贝塞尔曲线。如下图:
三阶贝塞尔曲线的公式如下:
2.3 贝塞尔一阶曲线
看过了二次和三次曲线,更高次的贝塞尔曲线大家应该也知道要怎么画了吧。那么比二次曲线更简单的一次(线性)贝塞尔曲线存在吗?长什么样?根据前面的介绍,只要稍作思考,想必你也能猜出来了。哈!就是一条直线~
2.4 更复杂的贝塞尔曲线
更复杂的贝塞尔曲线原理是一样的,来看看更复杂的贝塞尔曲线,如下图:
好了原理篇就讲到这里吧,大家可以点击进入这个网站去加深一下理解。
3.android贝塞尔曲线简单实现
我们android也支持贝塞尔曲线,只不过sdk最多只支持3阶的。要想支持更多阶得找第三方的了。在android中我们看一下有那些用贝塞尔曲线实现的效果:
1.QQ小红点拖拽效果
2.一些炫酷的下拉刷新控件
3.阅读软件的翻书效果
4.一些平滑的折线图的制作
5.其他很多炫酷的动画效果
下面我们讲解一下如何用android实现2阶和3阶贝塞尔曲线。
画贝塞尔曲线我们需要用到的类除了cavans和paint,最重要是是path类,利用path的二个方法:
quadTo()//画二阶贝塞尔曲线
cubicTo()//画三阶贝塞尔曲线
3.1 画2阶贝塞尔曲线代码
Path path = new Path();
path.moveTo(start.x,start.y);
path.quadTo(control.x,control.y,end.x,end.y);
由上面我们知道2阶贝塞尔曲线需要有3个点:
start.x,start.y是起始点的(x,y)坐标
control.x,control.y是中间控制点的(x,y)坐标
end.x,end.y是结束点的(x,y)坐标
#3.2 画3阶贝塞尔曲线代码:
3.2 画3阶贝塞尔曲线代码
Path path = new Path();
path.moveTo(start.x, start.y);
path.cubicTo(control1.x, control1.y, control2.x,control2.y, end.x, end.y);
canvas.drawPath(path, mPaint);
由上面我们知道3阶贝塞尔曲线需要有4个点,所以在cubicTo方法只需要2个控制点和一个结束点。
4.讲解一个案例
我们来做一个小小的例子练习一下,以2阶的贝塞尔曲线为例,效果图如下:
代码如下:
public class MyLayout extends View {
private Point startPoint;//开始点
private Point endPoint;//结束点
private Point controlPoint;//随意变动的控制点
private Paint mPaint;
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
//初始化paint,没什么可说的。
private void initPaint(){
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
}
/**
* onlayout中定死2个起始点。
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int padding = getWidth()/6;
startPoint = new Point(padding,getHeight()/2);
endPoint = new Point(getWidth() - padding , getHeight()/2);
controlPoint = new Point(getWidth()/2,getHeight()/2);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawPoint(canvas);//画点
drawHelpLine( canvas);//画辅助线
drawBezierLine(canvas);//画贝塞尔曲线
}
/**
* 绘制贝塞尔曲线
*/
private void drawBezierLine(Canvas canvas){
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(8);
Path path = new Path();
path.moveTo(startPoint.x, startPoint.y);
path.quadTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
canvas.drawPath(path, mPaint);
}
/**
* 绘制辅助线
*/
private void drawHelpLine(Canvas canvas){
mPaint.setColor(Color.LTGRAY);
mPaint.setStrokeWidth(4);
canvas.drawLine(startPoint.x,startPoint.y,controlPoint.x,controlPoint.y,mPaint);
canvas.drawLine(endPoint.x,endPoint.y,controlPoint.x,controlPoint.y,mPaint);
}
/**
* 画起始点和控制点
*/
private void drawPoint(Canvas canvas){
// 绘制数据点和控制点
mPaint.setColor(Color.GRAY);
mPaint.setStrokeWidth(20);
canvas.drawPoint(startPoint.x,startPoint.y,mPaint);
canvas.drawPoint(endPoint.x,endPoint.y,mPaint);
canvas.drawPoint(controlPoint.x,controlPoint.y,mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 根据触摸位置更新控制点,并提示重绘
controlPoint.x = (int) event.getX();
controlPoint.y = (int) event.getY();
invalidate();
return true;
}
}
这个代码挺简单的,我就不做详细介绍了,大家再想一下,把上面的2阶变成3阶怎么办,其实也很简单,再加一个控制点就行,代码如下:
// 绘制贝塞尔曲线
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(8);
Path path = new Path();
path.moveTo(start.x, start.y);
path.cubicTo(control1.x, control1.y, control2.x,control2.y, end.x, end.y);
canvas.drawPath(path, mPaint);
效果图如下:
5 补充Path类的API
这里对path类中的方法补充一下。
作用 | 相关方法 | 备注 |
---|---|---|
移动起点 | moveTo 移动下一次操作的起点位置 | |
设置终点 | setLastPoint 重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveTo相同 | |
连接直线 | lineTo 添加上一个点到当前点之间的直线到Path | |
闭合路径 | close 连接第一个点连接到最后一个点,形成一个闭合区域 | |
添加内容 | addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 添加(矩形, 圆角矩形, 椭圆, 圆, 路径, 圆弧) 到当前Path (注意addArc和arcTo的区别) | |
是否为空 | isEmpty 判断Path是否为空 | |
是否为矩形 | isRect 判断path是否是一个矩形 | |
替换路径 | set 用新的路径替换到当前路径所有内容 | |
偏移路径 | offset 对当前路径之前的操作进行偏移(不会影响之后的操作) | |
贝塞尔曲线 | quadTo, cubicTo 分别为二次和三次贝塞尔曲线的方法 | |
rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo 不带r的方法是基于原点的坐标系(偏移量),rXxx方法是基于当前点坐标系(偏移量) | |
填充模式 | setFillType, getFillType, isInverseFillType, toggleInverseFillType 设置,获取,判断和切换填充模式 | |
提示方法 | incReserve 提示Path还有多少个点等待加入(这个方法貌似会让Path优化存储结构) | |
布尔操作(API19) | op 对两个Path进行布尔运算(即取交集、并集等操作) | |
计算边界 | computeBounds 计算Path的边界 | |
重置路径 | reset, rewind 清除Path中的内容(reset相当于重置到new Path阶段,rewind会保留Path的数据结构) | |
矩阵操作 | transform 矩阵变换 |
6.结尾
好了就讲到这里吧,下一章我们做一个文章开头的效果。
在技术上我依旧是个小渣渣,加油,勉励自己!
7.参考文档
【1】Android 贝塞尔曲线的浅析,android贝塞尔浅析
【2】玩玩这个网站加深对贝塞尔曲线的理解
【3】贝塞尔曲线扫盲
【4】安卓自定义View进阶 - 贝塞尔曲线