贝塞尔曲线

6,990 阅读6分钟

为什么要讲贝塞尔曲线,实际上 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/bezier-curv…

这里推荐 医生 的一片博文,已经非常全面的介绍了贝塞尔曲线在 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/Be… ( 通过 de Casteljau 算法绘制贝塞尔曲线,并计算它的切线,实现 1-7 阶贝塞尔曲线的形成动画 )

参考博客:

Android 绘制 N 阶 Bezier 曲线