本文参考:https://www.jianshu.com/p/0c9b4b681724 https://www.jianshu.com/p/3b2ac05ae3c7
1 定义
贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。贝塞尔曲线是计算机图形学中相当重要的参数曲线。
2 简介
2.1 二阶贝塞尔曲线
二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
公式很难理解,但转换成图像就很简单了。
第一步:在平面内找三个不在同一条直线的点,然后依次连接,不闭合。
第二步:在AB和BC线段上找出点D和点E,使得 AD/AB = BE/BC。
第三步:连接DE,在DE上寻找点F,F点需要满足:DF/DE = AD/AB = BE/BC。
第四步:找到所有的F点,将这些点连接起来,结果如下图所示。
2.2 三阶贝塞尔曲线
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。公式如下所示:
转换成图像看一下,其实和二阶贝塞尔曲线大致相同,只是稍微复杂一些。
第一步:平面内选4个不同线的点并且依次用线段连接(也就是三条线)。
第二步:同二阶贝塞尔曲线一样首先需要在线段上找对应的点(E、F、G),对应的点必须要符合等比的计算规则,计算规则如下:AE/AB = BF/BC = CG/CD;找到对应的点以后接着依次链接EF、FG;接着在EF、FG线段上面继续找点H、I,对应的点依旧要符合等比的计算规则,也就是 EH/EF = FI/FG;最后连接H、I线段,在HI线段上面继续找点J、点J的计算规则需要符合:EH/EF = FI/FG = HJ/HI。
第三步:将所有的J连接起来即可,最后的曲线效果如下图所示。
2.3 n阶贝塞尔曲线
n阶贝兹曲线可如下推断。给定点P0、P1、…、Pn,其贝兹曲线即:
其本质上与二阶,三阶贝塞尔曲线相同,这里再贴一个四阶贝塞尔曲线的图。
2.4 一阶贝塞尔曲线
给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。这条线由下式给出:
你可能会问这一阶贝塞尔曲线有什么用,直线不是想画随便画。其实这还是有用处的,在Android属性动画,系统为我们提供了一个PathInterpolator插值器。这个PathInterpolator里面就有贝塞尔曲线的身影。
3 应用
接下来就是便是再Android系统上的实际应用。
3.1 API
Path方法 | 简介 |
---|---|
mPath.moveTo() | 移动到操作起始点坐标,不会进行绘制,只用于移动移动画笔 |
mPath.lineTo() | 从一个点连线到另一个点,用于进行直线绘制 |
mPath.quadTo(x1, y1, x2, y2) | 生成二次贝塞尔曲线,(x1,y1) 为控制点,(x2,y2)为结束点 |
mPath.cubicTo(x1, y1, x2, y2, x3, y3) | 生成三次贝塞尔曲线, (x1,y1) 为控制点,(x2,y2)为控制点,(x3,y3) 为结束点;即与二阶区别多一个控制点 |
3.2 二阶贝塞尔曲线
public class TwoBezierView extends View {
private Context mContext;
private Paint mPaint;
private int eventX, eventY;
private int centerX, centerY;
private int startX, startY;
private int endX, endY;
public TwoBezierView(Context context) {
super(context);
mContext = context;
init();
}
public TwoBezierView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public TwoBezierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w / 2;
centerY = h / 2;
startX = centerX - 250;
startY = centerY;
endX = centerX + 250;
endY = centerY;
eventX = centerX;
eventY = centerY - 250;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(Color.GRAY);
canvas.drawCircle(startX, startY, 8, mPaint);
canvas.drawCircle(endX, endY, 8, mPaint);
canvas.drawCircle(eventX, eventY, 8, mPaint);
mPaint.setStrokeWidth(3);
canvas.drawLine(startX, startY, eventX, eventY, mPaint);
canvas.drawLine(eventX, eventY, endX, endY, mPaint);
mPaint.setColor(Color.GREEN);
mPaint.setStyle(Paint.Style.STROKE);
Path path = new Path();
path.moveTo(startX, startY);
path.quadTo(eventX, eventY, endX, endY);
canvas.drawPath(path, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:{
eventX = (int) event.getX();
eventY = (int) event.getY();
invalidate();
break;
}
}
return true;
}
}
3.3 三阶贝塞尔曲线
public class ThreeBezierView extends View {
private Context mContext;
private Paint mPaint;
private int leftX, leftY;
private int rightX, rightY;
private int startX, startY;
private int endX, endY;
private int centerX, centerY;
private boolean leftOrRight;
public ThreeBezierView(Context context) {
super(context);
init(context);
}
public ThreeBezierView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ThreeBezierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mContext = context;
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w / 2;
centerY = h / 2;
startX = centerX - 250;
startY = centerY;
leftX = centerX - 250;
leftY = centerY - 250;
rightX = centerX + 250;
rightY = centerY - 250;
endX = centerX + 250;
endY = centerY;
leftOrRight = true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(Color.GRAY);
canvas.drawCircle(startX, startY, 8, mPaint);
canvas.drawCircle(leftX, leftY, 8, mPaint);
canvas.drawCircle(rightX, rightY, 8, mPaint);
canvas.drawCircle(endX, endY, 8, mPaint);
mPaint.setStrokeWidth(3);
canvas.drawLine(startX, startY, leftX, leftY, mPaint);
canvas.drawLine(leftX, leftY, rightX, rightY, mPaint);
canvas.drawLine(rightX, rightY, endX, endY, mPaint);
mPaint.setColor(Color.GREEN);
mPaint.setStyle(Paint.Style.STROKE);
Path path = new Path();
path.moveTo(startX, startY);
path.cubicTo(leftX, leftY, rightX, rightY, endX, endY);
canvas.drawPath(path, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE: {
if (leftOrRight) {
leftX = (int) event.getX();
leftY = (int) event.getY();
} else {
rightX = (int) event.getX();
rightY = (int) event.getY();
}
invalidate();
break;
}
}
return true;
}
public void setLeft() {
leftOrRight = true;
}
public void setRight() {
leftOrRight = false;
}
}
4总结
贝塞尔曲线本身是比较容易理解的,但是却有广泛的用处。