白话经典贝塞尔曲线及其在 Android 中的应用

1、前言

谈到贝塞尔曲线可能很多人会浮现它高大上的数学公式。然而,在实际应用中,并不须要咱们去彻底理解或者推导出公式才能应用得上。实际状况是,即便真的只是一个学渣,咱们应该也能很轻松的掌握贝塞尔曲线的大体原理及其在开发中的实际应用。html

2、白话贝塞尔曲线的原理

贝塞尔曲线有一阶、二阶、三阶....一直到 N 阶。实际应用中咱们经常使用的是二阶、三阶,高阶能够由低阶来实现。我们这里以二阶为例来说解一下贝塞尔曲线的基本原理。git

二阶贝塞尔曲线三要素github

1 个起点,1 个终点,1 个控制点canvas

这个是咱们要知道的第一个知识点,而三阶的话就是 2 个控制点,四阶的话就是 3 个,以此类推,N 阶的话就是 N - 1 个控制点。而起点和终点始终只有一个。bash

下面咱们来手动画一个贝塞尔曲线。app

  1. 绘制 1 个起点,1 个终点和 1 个控制点,分别为 S 、E、C。而后将 SC、CE 分别连线。以下图所示。

二阶贝塞尔一.jpg

  1. 从点 S 向 C 出发找到一个 D 点,从 C 向 E 出发找到一个 F 点,使得

SD / SC = CF / CEpost

而后链接 DF。以下图所示。动画

二阶贝塞尔二.jpg

  1. 在 DF 之间找到点 M,使得

SD / SC = CF / CE = DM / DFui

二阶贝塞尔三.jpg

总结下: (1) 二阶贝塞尔中,起初是 3 个点,而后咱们再找 2 个点,而后再找 1 个点。这个点就是咱们要找到的点。 (2) 咱们须要由 S 向 C 出发,由 C 向 E 出现,找到全部的 D 和 F,再找到全部的 M。 (3) 将全部的 M 链接起来就构造出了最后的所须要的贝塞尔曲线了。this

对于更高阶的三阶甚至N阶,其过程是同样的。再借用一个图,来详细观察一下其构造的过程。

二阶贝塞尔曲线.gif

关于贝塞尔曲线的原理,介绍这么多就够了,再说就是多余。若是实在还不明白或者想深刻的,推荐如下连接。固然,更建议本身多琢磨琢磨。

贝塞尔曲线 总结 贝塞尔曲线扫盲 贝塞尔曲线游戏 bezier-circle

3、贝塞尔曲线在 Android 自定义 View 中的实战

Android 中提供了一个 Path 类,其有 2 个方法 Path#quadTo() 和 Path#cubicTo()。分别用于构造二阶和三阶贝塞尔曲线的。其原型分别以下。

quadTo() 方法

/**
     * Add a quadratic bezier from the last point, approaching control point
     * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
     * this contour, the first point is automatically set to (0,0).
     *
     * @param x1 The x-coordinate of the control point on a quadratic curve
     * @param y1 The y-coordinate of the control point on a quadratic curve
     * @param x2 The x-coordinate of the end point on a quadratic curve
     * @param y2 The y-coordinate of the end point on a quadratic curve
     */
    public void quadTo(float x1, float y1, float x2, float y2) 
复制代码

cubicTo() 方法

/**
     * Add a cubic bezier from the last point, approaching control points
     * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
     * made for this contour, the first point is automatically set to (0,0).
     *
     * @param x1 The x-coordinate of the 1st control point on a cubic curve
     * @param y1 The y-coordinate of the 1st control point on a cubic curve
     * @param x2 The x-coordinate of the 2nd control point on a cubic curve
     * @param y2 The y-coordinate of the 2nd control point on a cubic curve
     * @param x3 The x-coordinate of the end point on a cubic curve
     * @param y3 The y-coordinate of the end point on a cubic curve
     */
    public void cubicTo(float x1, float y1, float x2, float y2,
                        float x3, float y3)
复制代码

参数中,后面的点表明终点,中间的点表明控制点。看起来高大上的贝塞尔曲线,在 Android 中咱们主要掌握这 2 个方法的应用基本就可拿下了。固然,实际开发过程当中,还有其余问题须要解决,好比拆解,寻找起点,终点和控制点等有时候也是技术活。

下面来看两个实际的案例。

完整的 demo github.com/ly20050516/…。对于不喜欢看文字讲解的,能够直接下载源码进行调试看效果。

3.1 经典案例 —— 流动的水波

先上效果图。

wave.gif

主要步骤以下:

流动的水波.jpg

核心代码以下:

// 重置 path
        path.reset();
        // 将 path 移到起点 (0,h)
        path.moveTo(0,h);
        // 绘制第 1 部分,终点为 (w / 2,h),控制点为 (w / 4,h + WAVE_AMPLITTUDE),获得一条下凹的曲线
        path.quadTo(w / 4,h + WAVE_AMPLITTUDE,w / 2,h);
        // 第 2 部分再以 (w / 2,h) 为起点,以 (w,h) 为终点,以 (w * 3 / 4,h - WAVE_AMPLITTUDE) 为控制点,获得一条上凸的曲线
        path.quadTo(w * 3 / 4,h - WAVE_AMPLITTUDE,w,h);
        // 第 3 部分和第 4 部分就是重复第 1 部分和第 2 部分。只是注意坐标的计算
        path.quadTo(w * 5 / 4,h + WAVE_AMPLITTUDE,w * 3 / 2,h);
        path.quadTo(w * 7 / 4,h - WAVE_AMPLITTUDE,w * 2,h);
        // 而后将 path 封闭获得一填充区域
        path.lineTo(w * 2,getHeight());
        path.lineTo(0,getHeight());
        path.close();

        // 下面的 offset 由属性动画来控制其值,变化范围为 (0,width)
        matrix.reset();
        // 随着动画的不断更新来变换 path 的 offset,从而造成流动的动画
        matrix.postTranslate(-offset,0);
        path.transform(matrix);

        // 最后绘制出须要的曲面,对,不是曲线了
        canvas.drawPath(path,paint);
复制代码

3.2 经典案例 —— 仿 QQ 拖拽清除 tips

效果图以下

qq.gif

主要步骤:

仿 QQ 擦除.jpg

核心代码:

// 计算 2 点之间的距离
        float distance = (float) Math.sqrt(Math.pow((tipsViewMoveX - tipsViewX), 2) + Math.pow((tipsViewMoveY - tipsViewY), 2));
        // 圆的半径随着距离愈来愈远变和愈来愈小
        radius = -distance / 15 + DEFAULT_RADIUS;

        if (radius <= 0) {
            isLimit = true;
            return;
        }

        if(tipsViewMoveX - tipsViewX == 0 || tipsViewMoveY - tipsViewY == 0) {
            return;
        }

        /**
         * 计算偏移量 offsetX 以及 offsetY
         *
         * 直线的斜率 k = (y2 - y1) / (x2 - x1) = tan𝞪,因此这里 Math.atan(k) 就是计算出来的角度,再根据角度分别计算出 offsetX 与 offsetY。
         *
         */
        float offsetX = (float) (radius * Math.sin(Math.atan((tipsViewMoveY - tipsViewY) / (tipsViewMoveX - tipsViewX))));
        float offsetY = (float) (radius * Math.cos(Math.atan((tipsViewMoveY - tipsViewY) / (tipsViewMoveX - tipsViewX))));

        float x1 = tipsViewX - offsetX;
        float y1 = tipsViewY + offsetY;

        float x2 = tipsViewMoveX - offsetX;
        float y2 = tipsViewMoveY + offsetY;

        float x3 = tipsViewMoveX + offsetX;
        float y3 = tipsViewMoveY - offsetY;

        float x4 = tipsViewX + offsetX;
        float y4 = tipsViewY - offsetY;

        // 重置 path
        path.reset();
        // 移到点 (x1,y1)
        path.moveTo(x1,y1);
        // 以 (x1,y1) 为起点,(x2,y2) 为终点,控制点为两圆心的中心点,画一条二阶贝塞尔曲线
        path.quadTo(controllerX,controllerY,x2,y2);
        // 画直线
        path.lineTo(x3,y3);
        // 再对称画一条二阶贝塞尔曲线
        path.quadTo(controllerX,controllerY,x4,y4);
        // 画直线,封闭区域
        path.lineTo(x1,y1);
复制代码

4、总结

贝塞尔曲线在 Android 中的应用自己并不难,主要掌握好 Path#quadTo() 和 Path#cubicTo() 这两个方法的使用便可。难就难在对目标图形的拆分以及计算起点,终点和控制点上。

相关文章
相关标签/搜索