编程之道
首发于编程之道
贝塞尔曲线

贝塞尔曲线

实际上 Android 中很多效果都有用到贝塞尔曲线。

QQ 的消息拽拖小红点旗袍消失的效果

QQ空间 直播页面右下角的礼物冒泡特效

水流波动效果

图片或书本翻页效果

一个弹性效果的抽屉菜单


先对贝塞尔曲线有一个大概的认识。

贝赛尔曲线-维基百科

历史

贝塞尔曲线的数学基础是早在 1912 年就广为人知的 伯恩斯坦多项式 。但直到 1959 年,当时就职于雪铁龙的法国数学家 Paul de Casteljau 才开始对它进行图形化应用的尝试,并提出了一种数值稳定的 de Casteljau 算法。然而贝塞尔曲线的得名,却是由于 1962 年另一位就职于雷诺的法国工程师 Pierre Bézier 的广泛宣传。他使用这种只需要很少的控制点就能够生成复杂平滑曲线的方法,来辅助汽车车体的工业设计。

正是因为控制简便却具有极强的描述能力,贝塞尔曲线在工业设计领域迅速得到了广泛的应用。不仅如此,在计算机图形学领域,尤其是矢量图形学,贝塞尔曲线也占有重要的地位。今天我们最常见的一些矢量绘图软件,如 Flash、Illustrator、CorelDraw 等,无一例外都提供了绘制贝塞尔曲线的功能。甚至像 Photoshop 这样的位图编辑软件,也把贝塞尔曲线作为仅有的矢量绘制工具(钢笔工具)包含其中。

贝塞尔曲线在 Web 开发领域同样占有一席之地。CSS3 新增了 transition-timing-function 属性,它的取值就可以设置为一个三次贝塞尔曲线方程。在此之前,也有不少 JavaScript 动画库使用贝塞尔曲线来实现美观逼真的缓动效果。

公式

线性贝塞尔曲线

给定点P0、P1,线性贝塞尔曲线只是一条两点之间的直线。这条线由下式给出:

二次方贝塞尔曲线

二次方贝塞尔曲线的路径由给定点P0、P1、P2的函数B(t)追踪:

三次方贝塞尔曲线

P0、P1、P2、P3 四个点在平面或在三维空间中定义了三次方贝塞尔曲线。曲线起始于 P0 走向 P1 ,并从 P2 的方向来到 P3 。一般不会经过 P1 或 P2 ;这两个点只是在那里提供方向资讯。 P0 和 P1 之间的间距,决定了曲线在转而趋进 P2 之前,走向 P1 方向的“长度有多长”。

曲线的参数形式为:

n阶贝塞尔曲线

n阶贝塞尔曲线可如下判断,给定点P0、p1、...、Pn,其贝塞尔曲线即

看公式还是有些抽象,接下来可以看一下图解,具像的了解一下什么是贝塞尔曲线:以二阶贝塞尔曲线和三阶贝塞尔曲线为例。

有一篇文章写的极好

贝塞尔曲线扫盲

贝塞尔曲线不仅能画直线,也能画曲线。即便是更复杂的曲线,控制点的增加也只是线性的。这一特点使其不光在工业设计领域大展拳脚,就连数学基础不好的人也可以比较容易地掌握,比如大多数平面美术设计师们。

简单来说,贝塞尔曲线就是将任意一条曲线转化为准确的数学公式。 **Bezier** 曲线是用一系列点来控制曲线状态的。

一些关键的名词

  • 数据点:通常是指一条路径的起始点和终止点
  • 控制点:控制点决定了一条路径的弯曲轨迹,根据控制点的个数,贝塞尔曲线被分为一阶贝塞尔曲线(0个控制点)、二阶贝塞尔曲线(1个控制点)、三阶贝塞尔曲线(2个控制点),以此类推。

在Android中的应用

在 Android 开发中,我们只考虑二阶贝塞尔曲线和三阶贝塞尔曲线, SDK 也是只提供了二阶和三阶的 API 调用。

当然,对于再高阶的贝塞尔曲线,通常可以拆分成多个低阶的贝塞尔曲线。

推荐一个好的贝塞尔曲线的动态演示,可以很直观的感受一下:

myst729.github.io/bezie

这里推荐 医生 的一片博文,已经非常全面的介绍了贝塞尔曲线在 Android 中的一些用法及实例,写的很好。

贝塞尔曲线开发的艺术

下面的内容都基于这篇文章。

二阶贝塞尔曲线在 Android 中的 API 为:


quadTo 是基于绝对坐标,而 rQuadTo 是基于相对坐标。

mPath.moveTo(mStartPointX, mStartPointY);

//二阶贝塞尔曲线

mPath.quadTo(mAuxiliaryX, mAuxiliaryY, mEndPointX, mEndPointY);

canvas.drawPath(mPath, mPaintBezier);

三阶贝塞尔曲线在 Android 中的 API 为:

这两个API在原理上也是可以互相转换的。

两个点的话涉及到多点触摸,有兴趣可以看看下面这篇文章。

Android MotionEvent详解

cubicTo是基于绝对坐标,而 rCubicTo 是基于相对坐标。

mPath.moveTo(mStartPointX, mStartPointY);

// 三阶贝塞尔曲线

mPath.cubicTo(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mEndPointX, mEndPointY);

canvas.drawPath(mPath, mPaintBezier);

三阶贝塞尔曲线其实也没什么,就是多了一个控制点,记录下他的位置就可以了。

圆滑绘图

当在屏幕上绘制路径的时候,我们通常会通过 Path.lineTo 将各个触点连接起来,但是这样肯定会很生硬。因为是通过直线来连接的。如果通过二阶贝塞尔曲线,就会圆滑很多。不会出现太多的生硬连接。

if (dx >= offset || dy >= offset) {

                    // 贝塞尔曲线的控制点为起点和终点的中点

                    float cX = (x1 + preX) / 2;

                    float cY = (y1 + preY) / 2;

                    mPath.quadTo(preX, preY, cX, cY);

//                    mPath.lineTo(x1, y1);

                    mX = x1;

                    mY = y1;

                }

通过纪录 Move 过程中点位置的变化,然后传入 quadTo 中的参数,就可以实现圆滑的绘图。

根据公式我们可以模仿着写一个工具类:

public class BezierUtil {



/**

* B(t) = (1 - t)^2 * P0 + 2t * (1 - t) * P1 + t^2 * P2, t ∈ [0,1]

*

* @param t  曲线长度比例

* @param p0 起始点

* @param p1 控制点

* @param p2 终止点

* @return t对应的点

*/

public static PointF CalculateBezierPointForQuadratic(float t, PointF p0, PointF p1, PointF p2) {

PointF point = new PointF();

float temp = 1 - t;

point.x = temp * temp * p0.x + 2 * t * temp * p1.x + t * t * p2.x;

point.y = temp * temp * p0.y + 2 * t * temp * p1.y + t * t * p2.y;

return point;

}



/**

* B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3, t ∈ [0,1]

*

* @param t  曲线长度比例

* @param p0 起始点

* @param p1 控制点1

* @param p2 控制点2

* @param p3 终止点

* @return t对应的点

*/

public static PointF CalculateBezierPointForCubic(float t, PointF p0, PointF p1, PointF p2, PointF p3) {

PointF pointF = new PointF();

float temp = 1- t;

pointF.x = p0.x * temp * temp * temp + 3 * p1.x * t * temp * temp + 3 * p2.x * t * t * temp + p3.x * t * t * t;

pointF.y = p0.y * temp * temp * temp + 3 * p1.y * t * temp * temp + 3 * p2.y * t * t * temp + p3.y * t * t * t;

return pointF;

}

}

推荐一个关于贝塞尔曲线的开源项目:

github.com/venshine/Bez ( 通过 de Casteljau 算法绘制贝塞尔曲线,并计算它的切线,实现 1-7 阶贝塞尔曲线的形成动画 )

参考博客:

Android 绘制 N 阶 Bezier 曲线

编辑于 2016-11-26

文章被以下专栏收录