前段时间做了一个手写板功能的东西,其中手写部分用二次贝塞尔曲线完成,今天就来总结一下贝塞尔曲线在Android中的应用,先简单介绍各阶贝塞尔曲线的原理,然后实现通过贝塞尔曲线实现波浪线功能,感兴趣的同学继续看下去吧!
在数学的数值分析领域中,贝塞尔曲线(英语:Bézier curve,亦作“贝塞尔”)是计算机图形学中相当重要的参数曲线。更高维度的广泛化贝塞尔曲线就称作贝兹曲面,其中贝兹三角是一种特殊的实例。
贝塞尔曲线于1962年,由法国工程师皮埃尔·贝兹(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由保尔·德·卡斯特里奥于1959年运用德卡斯特里奥算法开发,以稳定数值的方法求出贝塞尔曲线。
贝塞尔曲线主要用于二维图形应用程序中的数学曲线,曲线由起始点,终止点(也称锚点)和控制点组成,通过调整控制点,通过一定方式绘制的贝塞尔曲线形状会发生变化。根据方程的最高阶数,又分为线性贝赛尔曲线,二阶贝塞尔曲线、三阶贝塞尔曲线和高阶贝塞尔曲线。
一阶贝塞尔曲线有两个点,一个是贝塞尔曲线的起点,一个是曲线的终点,主要就是画一条线段
线性贝塞尔曲线函数中的t会经过由P0至P1的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0至P1路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线。
二阶贝塞尔曲线有三个点,一个是贝塞尔曲线的起点,一个是曲线控制点,一个是曲线终点,主要就是用来画不是很复杂的曲线,例如上面说的手写板功能,使用的就是二阶贝塞尔曲线
为建构二次贝塞尔曲线,可以中介点Q0和Q1作为由0至1的t:
p0是起始点,p1是控制点,p2是终点
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝塞尔曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P2之前,走向P1方向的“长度有多长”。
对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0、Q1、Q2,和由二次曲线描述的点R0、R1所建构:
不管是几阶,原理都一样,感兴趣的可以自己证明一下这个原理,下面给出四阶的原理图
贝塞尔曲线在android中主要用于各种自定义view,主要是实现曲线更平滑,例如上面说的手写板,波浪线等等,在Android中用的最多的是二阶和三阶贝塞尔曲线,下面我们一起用二阶贝塞尔曲线写一个实例了解一下在Android中的用法,最后附上贝塞尔曲线实现水波纹效果
二阶贝塞尔曲线:
quadTo(x1,y1,x2,y2)
rQuadTo(dx1,dy1,dx2,dy2)
public class SecondOrderBezierView extends View {
Path mPath;
Paint mPaint;
Paint mLinePaint;
float centerX, centerY;
float mX, mY;
float startX, startY;
float endX, endY;
public SecondOrderBezierView(Context context) {
super(context);
init();
}
public SecondOrderBezierView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(10f);
mPaint.setStyle(Paint.Style.STROKE);
mLinePaint = new Paint();
mLinePaint.setAntiAlias(true);
mLinePaint.setColor(Color.GRAY);
mLinePaint.setStrokeWidth(2f);
mLinePaint.setStyle(Paint.Style.STROKE);
mPath = new Path();
}
@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;
mX = centerX;
mY = centerY - 500;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
mX = event.getX();
mY = event.getY();
break;
}
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
mPath.moveTo(startX, startY);
mPath.quadTo(mX, mY, endX, endY);
canvas.drawPath(mPath, mPaint);
canvas.drawLine(startX, startY, mX, mY, mLinePaint);
canvas.drawLine(mX, mY, endX, endY, mLinePaint);
}
}
三阶贝塞尔曲线
cubicTo(float x1, float y1, float x2, float y2,
float x3, float y3)
rCubicTo(float x1, float y1, float x2, float y2,
float x3, float y3)
实例省略,跟二阶贝塞尔曲线差不多,多了一个控制点
学习了贝塞尔曲线,下面来实际操作一下
一起实现波浪效果,首先先画出基本的波浪形状,起点,控制点,终点确定之后先画出一段的波浪线,再修改起点,终点,控制点画第二段波浪线
mPath.moveTo(startX, startY);
mPath.quadTo(mX, mY, centerX, centerY);
mPath.quadTo(centerX * 3 / 2, centerY + 300, endX, endY);
然后通过修改Paint的Style为FILL_AND_STROKE,通过path的lineto把下面部分填充
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPath.lineTo(2 * centerX, 2 * centerY);
mPath.lineTo(0, 2 * centerY);
最后通过动画让波浪动起来, 说明由于使用的平移动画,所以需要在屏幕外多画一个完整的波浪线
demo仅供参考,当然也可以通过其他方式实现
public class WaveView extends View {
Paint mPaint;
Path mPath;
float mX, mY, centerX, centerY, startX, startY, endX, endY;
int waveCount, waveLength, mOffsetX;
ValueAnimator mValueAnimator;
public WaveView(Context context) {
super(context);
init();
}
public WaveView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(10f);
mPaint.setColor(Color.RED);
mPath = new Path();
waveLength = 800;
startWave();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
waveLength = w;
centerX = w / 2;
centerY = h / 2;
startX = 0;
startY = centerY;
endX = w;
endY = centerY;
mX = centerX / 2;
mY = centerY - 300;
waveCount = (int) Math.round(w / waveLength + 1.5);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
mPath.moveTo(-waveLength + mOffsetX, centerY);
for (int i = 0; i < waveCount; i++) {
mPath.rQuadTo(waveLength / 4, -100, waveLength / 2, 0);
mPath.rQuadTo(waveLength / 4, +100, waveLength / 2, 0);
}
mPath.lineTo(2 * centerX, 2 * centerY);
mPath.lineTo(0, 2 * centerY);
mPath.close();
canvas.drawPath(mPath, mPaint);
}
void startWave() {
mValueAnimator = ValueAnimator.ofInt(0, waveLength);
mValueAnimator.setDuration(1000);
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mOffsetX = (int) valueAnimator.getAnimatedValue();
invalidate();
}
});
mValueAnimator.start();
}
}
贝塞尔曲线
下面是我的公众号,不定时发送文章,欢迎关注