贝塞尔曲线开发的艺术
一句话概括贝塞尔曲线:将任意一条曲线转化为精确的数学公式。
很多绘图工具中的钢笔工具,就是典型的贝塞尔曲线的应用,这里的一个网站可以在线模拟钢笔工具的使用:
贝塞尔曲线中有一些比较关键的名词,解释如下:
- 数据点:通常指一条路径的起始点和终止点
- 控制点:控制点决定了一条路径的弯曲轨迹,根据控制点的个数,贝塞尔曲线被分为一阶贝塞尔曲线(0个控制点)、二阶贝塞尔曲线(1个控制点)、三阶贝塞尔曲线(2个控制点)等等。
要想对贝塞尔曲线有一个比较好的认识,可以参考WIKI上的链接:
https://en.wikipedia.org/wiki/B%C3%A9zier_curve
贝塞尔曲线模拟
在Android中,一般来说,开发者只考虑二阶贝塞尔曲线和三阶贝塞尔曲线,SDK也只提供了二阶和三阶的API调用。对于再高阶的贝塞尔曲线,通常可以将曲线拆分成多个低阶的贝塞尔曲线,也就是所谓的降阶操作。下面将通过代码来模拟二阶和三阶的贝塞尔曲线是如何绘制和控制的。
贝塞尔曲线的一个比较好的动态演示如下所示:
http://myst729.github.io/bezier-curve/
二阶模拟
二阶贝塞尔曲线在Android中的API为:quadTo()和rQuadTo(),这两个API在原理上是可以互相转换的——quadTo是基于绝对坐标,而rQuadTo是基于相对坐标,所以后面我都只以其中一个来进行讲解。
先来看下最终的效果:
从前面的介绍可以知道,二阶贝塞尔曲线有两个数据点和一个控制点,只需要在代码中绘制出这些辅助点和辅助线即可,同时,控制点可以通过onTouchEvent来进行传递。
package com.xys.animationart.views;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;/** * 二阶贝塞尔曲线 * <p/> * Created by xuyisheng on 16/7/11. */public class SecondOrderBezier extends View {
private Paint mPaintBezier; private Paint mPaintAuxiliary; private Paint mPaintAuxiliaryText; private float mAuxiliaryX; private float mAuxiliaryY; private float mStartPointX; private float mStartPointY; private float mEndPointX; private float mEndPointY; private Path mPath = new Path(); public SecondOrderBezier(Context context) { super(context); } public SecondOrderBezier(Context context, AttributeSet attrs) { super(context, attrs); mPaintBezier = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintBezier.setStyle(Paint.Style.STROKE); mPaintBezier.setStrokeWidth(8); mPaintAuxiliary = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintAuxiliary.setStyle(Paint.Style.STROKE); mPaintAuxiliary.setStrokeWidth(2); mPaintAuxiliaryText = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintAuxiliaryText.setStyle(Paint.Style.STROKE); mPaintAuxiliaryText.setTextSize(20); } public SecondOrderBezier(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mStartPointX = w / 4; mStartPointY = h / 2 - 200; mEndPointX = w / 4 * 3; mEndPointY = h / 2 - 200; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.reset(); mPath.moveTo(mStartPointX, mStartPointY); // 辅助点 canvas.drawPoint(mAuxiliaryX, mAuxiliaryY, mPaintAuxiliary); canvas.drawText("控制点", mAuxiliaryX, mAuxiliaryY, mPaintAuxiliaryText); canvas.drawText("起始点", mStartPointX, mStartPointY, mPaintAuxiliaryText); canvas.drawText("终止点", mEndPointX, mEndPointY, mPaintAuxiliaryText); // 辅助线 canvas.drawLine(mStartPointX, mStartPointY, mAuxiliaryX, mAuxiliaryY, mPaintAuxiliary); canvas.drawLine(mEndPointX, mEndPointY, mAuxiliaryX, mAuxiliaryY, mPaintAuxiliary); // 二阶贝塞尔曲线 mPath.quadTo(mAuxiliaryX, mAuxiliaryY, mEndPointX, mEndPointY); canvas.drawPath(mPath, mPaintBezier); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: mAuxiliaryX = event.getX(); mAuxiliaryY = event.getY(); invalidate(); } return true; }}
- 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
三阶模拟
三阶贝塞尔曲线在Android中的API为:cubicTo()和rCubicTo(),这两个API在原理上是可以互相转换的——quadTo是基于绝对坐标,而rCubicTo是基于相对坐标,所以后面我都只以其中一个来进行讲解。
有了二阶的基础,再来模拟三阶就非常简单了,无非是增加了一个控制点而已,先看下效果图:
代码只需要在二阶的基础上添加一些辅助点即可,下面只给出一些关键代码,详细代码请参考Github:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.reset(); mPath.moveTo(mStartPointX, mStartPointY); // 辅助点 canvas.drawPoint(mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary); canvas.drawText("控制点1", mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliaryText); canvas.drawText("控制点2", mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliaryText); canvas.drawText("起始点", mStartPointX, mStartPointY, mPaintAuxiliaryText); canvas.drawText("终止点", mEndPointX, mEndPointY, mPaintAuxiliaryText); // 辅助线 canvas.drawLine(mStartPointX, mStartPointY, mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary); canvas.drawLine(mEndPointX, mEndPointY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary); canvas.drawLine(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary); // 三阶贝塞尔曲线 mPath.cubicTo(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mEndPointX, mEndPointY); canvas.drawPath(mPath, mPaintBezier); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
模拟网页
如下所示的网页,模拟了三阶贝塞尔曲线的绘制,可以通过拖动曲线来获取两个控制点的坐标,而起始点分别是(0,0)和(1,1)。
通过这个网页,也可以比较方便的获取三阶贝塞尔曲线的控制点坐标。
贝塞尔曲线应用
圆滑绘图
当在屏幕上绘制路径时,例如手写板,最基本的方法是通过Path.lineTo将各个触点连接起来,而这种方式在很多时候会发现,两个点的连接是非常生硬的,因为它毕竟是通过直线来连接的,如果通过二阶贝塞尔曲线来将各个触点连接,就会圆滑的多,不会出现太多的生硬连接。
先来看下代码,非常简单的绘制路径代码:
package com.xys.animationart.views;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;/** * 圆滑路径 * <p/> * Created by xuyisheng on 16/7/19. */public class DrawPadBezier extends View {
private float mX; private float mY; private float offset = ViewConfiguration.get(getContext()).getScaledTouchSlop(); private Paint mPaint; private Path mPath; public