android高级UI之贝塞尔曲线<上>---基本概念、德卡斯特里奥算法

在上一次https://www.cnblogs.com/webor2006/p/12712076.html对于Android UI绘制中核心的Canvas进行了相关的学习,这块的学习也中断一年多了,既然主业是Android开发,没有任何理由能停止对它的继续学习探索,所以接下来对于Android UI的学习继续。

贝塞尔曲线基本概念:

目标:

对于贝塞尔曲线的绘制其实在之前https://www.cnblogs.com/webor2006/p/7726174.html做QQ汽泡效果时已经用到过了,平常不自定义一些特殊的效果可能不一定能用到,但是!!!如果你不了解它的绘制,可能在需要它的时候你会很抓狂,因为它的使用也不是那么容易,我还记得早些年在一家公司叫我做一个电池充电过程中的水波纹效果就死活没有搞定,最后是让组里的一位大神帮忙解决了,当时的动画需求本身就比较复杂,网上也没有类似能满足要求的开源效果,我也就是对于贝塞尔曲线的绘制不了解导致做不出来,当时真的就是“书到用时方恨少”感受,所以接下来再来对它进行一个详细的了解,当作一个复习+巩固吧,最后还是以QQ汽泡的效果做为一个实战练习,效果如下:

整个效果用过手机版的QQ应该都比较熟悉了,不过这是没有集成到列表当中,对于列表当中如何集成也请移步到https://www.cnblogs.com/webor2006/p/7787511.html进行观赏。

那咱们这次主要学习的是怎么画“贝塞尔曲线”对吧,那对应这个QQ汽泡来说,哪里用到了呢?这里:

看到中间的上下两个曲线了吧,它们用到了,当然这是一个二阶贝塞尔【啥叫二阶贝塞尔呢?下面会学习到】,也是最最基础的,了解了基础的画法,对于它更加复杂的画法你也比较容易上手了,好,这是一个整体的目标。

整个学习会参考到这位大佬的简书:https://www.jianshu.com/p/95513310ff4d,不过我这边会“去其糟粕,取其精华”,最终目的就是吸收成自己的。

概念了解:

首先要明白,对于贝塞尔曲线,它是分阶的,不同的阶数,其绘制的曲线效果也不一样,所以这里先来了解不同阶数的贝塞尔曲线的绘制情况。

一阶贝塞尔曲线:

先看一下效果:

一条直线。。不是贝塞尔是用来绘制曲线的么?是的,因为对于一阶贝塞尔实际没啥用,但是对于你了解它的概念是一个基础,等于它的核心目的其实就只是由两点控制的一条直线,绘制就是从一点绘制到另一点的整个轨迹,记住这个绘制过程,因为在理解二阶贝塞尔曲线的过程中,需要使用到它。

二阶贝塞尔曲线【重点】:

绘制过程:

对于二阶贝塞尔曲线来说,应该是应用最最广泛的,也是核心中的核心,对于贝塞尔曲线不是还有更加高阶复杂的曲线么?而要理解更加复杂的贝塞尔曲线,理解二阶就成为一个必须要掌握的了,只有掌握了二阶的画法,你才能更加从容的面对更加复杂的贝塞尔曲线,所以这块务必要理解透。

先来直观的感受一下它的绘制过程:

嗯,此图来自于csdn,看完此效果之后是不是觉得挺酷炫的?而在上面QQ汽泡效果中的那个曲线,是不是跟这个二阶曲线能联系起来?这也就是为啥在之后的贝塞尔曲线的案例应用时QQ汽泡效果使用的是二阶贝塞尔的原因了,但是你能看懂它的绘制规则么?反正我光看这动图还是一脸懵逼的,所以接下来会了解其原理,这样才能真正理解它。

绘制原理:

先明显对于二阶贝塞尔曲线是由三个点来进行构成的,如下:

其中AC这两点本来是应该绘制一条直线的对吧:

但是该直线需要受B点往下进行拖动,这个B其实就是一个控制点,正因为有这个控制点才会让本应该绘直线的最终变成了绘制曲线了,其实在我们电脑的绘制软件中也能直观的感受一下这个B点的控制点的魅力,比如我mac上用Paintbrush这个软件有一个曲线的绘制:

 

我要绘制一条曲线,也是先绘制一条直线,然后再通过对直线的拖拽才完成曲线的绘制的,如下:

所以,此时应该对于B点它的出现的意义有了直观的了解了吧,接下来重点是就来看一下这个曲线的绘制规则了,这里先把两点到控制点连接两条直线,如下:

然后曲线的整体绘制是由AB这条一阶贝塞尔曲线来控制:

那AC的这根曲线很显然也是由一大堆的点的连续构成的对吧,让曲线上的绘制点又是如何来确定的呢?现在整个曲线的控制是由AB这个贝塞尔曲线来控制,它是一个运动的轨迹【这一点必须要理解到位,它是不断在走的,不明白的可以看一下一阶贝塞尔曲线的那个动图】,也就是AB上走了多少个点,就会对应的生成曲线上的各个点,这里以AB上的某一个静止点进行分析,把它如何确定最终曲线上的绘制点的搞清楚了,你也就明白了整个曲线的绘制原理了,比如AB上的贝塞尔曲线走到D这个点了:

其中这条直线上的AD跟DB就有一个比例关系了对吧,这时在BC上也取一个点E,其比例跟D点比例一样【注意关键词,比例要一样】,如下:

好,此时将DE进行一个连线:

接下来的绘制点就是在DE上了,那。DE上对应的绘制点是如何确定的呢?也是同样的套路,在这条直线上取一点F,要保证比例跟AD的一样,如下:

此时F点就是最终要绘制在曲线上的一个点【注意它只是曲线上的一个点哟】,如下:

曲线上的某个点我们已经知道怎么算出来了,我们由A-C开始启动绘制,则会算出曲线上的N个点,用PATH记录这个点进行绘制,从而y就得到了一条曲线,这条曲线就是所谓的贝塞尔曲线。这里再来回忆一下整个二阶贝塞尔曲线完整的绘制过程:

其中t表示整个曲线的点的次数,盯着那根绿色的那根线看,是不是能感受到比例正好跟P0到P1的一阶贝塞尔走的位置比例一样,截一个静态图说明一下:

计算公式:

其实对于二阶贝塞尔曲线在百度上能搜到相关的公式:

但是我看不太懂,其实有一个比较容易理解的计算公式,还是以这个图为例:

想要找到绘制点的话只需要遵守DF:DE= AD:AB= BE:BC,那么此时F点就是绘制点,因为比例是一样的。

三阶贝塞尔曲线:

绘制过程:

绘制原理:

其原理跟二阶的一样,看一下图:

从上图我们可以看到三阶比二阶多了一条线段,实际上表示的是最终开始点在A结束在B
中途会往C的方法有一定的移动,然后最终到D结束,计算方式有一定的区别,先由AB BC计算出一条线,在由BC CD 计算出第二条线构建一个二阶的贝塞尔,然后进行绘制,最终绘制的点是J ,同样满足这样的公式:AE:AB= BF:BC= CG:CD= EH:EF= FI:FG= HJ:HI。

更多阶贝塞尔曲线:

了解了一、二、三阶贝塞尔曲线之后,接着再来感受一下更多阶的效果,让你瞬间晕眩:

1、四阶贝塞尔曲线:

2、五阶贝塞尔曲线:

生成工具:

如果想看更多阶的效果,这里推荐一个针对贝塞尔曲线学习开源的一个代码:https://github.com/qingguoguo/BezierMaker,最多支持七阶,咱们把它下下来,看一下七阶的效果:

这对于学习贝塞尔曲线来说是一个不错的辅助工具,有兴趣的可以下下来跑一跑。

贝塞尔曲线的基本画法:

接下来咱们自己来体验画一画贝塞尔曲线吧,为之后的QQ消息气泡的实现打打基础。

二阶贝塞尔曲线:

对于Android的Path类中本身就已经提供有二、三阶曲线的API了,具体可以参考https://www.jianshu.com/p/12fcc3fedbbc:

而我在Android7.0的源码中貌似对于三创的API是这个方法名:

应该是有版本差异,这里就不过多纠结了,反正系统支持最多是3阶。 

这里以绘制二阶为例,来体验一下画法。

1、新建工程:

这里还是基于原来学习UI时搭建的工程新建一个module:

2、新建View:

接下来新建一个自定义View,为曲线的绘制搭好环境:

3、实现绘制逻辑:

1、定义控制点:

也就是我手指移动的位置就是一个控制点。

2、定义开始点和结束点:

对于三阶的贝塞尔曲线,除了控制点之外,还需要有一个开始点和结束点,这里定死一下:

3、定义画笔:

4、开始绘制:

这里直接使用path.quadTo()的api既可以绘制一个二阶贝塞尔曲线,如下:

整个代码如下:

package com.cexo.beziermaker.test;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * 绘制贝塞尔曲线
 */
public class BezierView1 extends View {

    //曲线开始点
    private float startX, startY;
    //结束点
    private float endX, endY;
    //控制点
    private float contorlX = 200, contorlY = 60;//默认值
    private Paint paint;
    private Path path;

    public BezierView1(Context context) {
        this(context, null);

        startX = 160;
        startY = 350;
        endX = 550;
        endY = 350;

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(4);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.BLACK);

        path = new Path();
    }

    public BezierView1(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public BezierView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, -1);
    }

    public BezierView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        path.reset();
        paint.setColor(Color.BLACK);
        path.moveTo(startX, startY);
        //二介曲线绘制方法
        path.quadTo(contorlX, contorlY, endX, endY);
        canvas.drawPath(path, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            contorlX = event.getX();
            contorlY = event.getY();
            invalidate();
        }
        return true;
    }
} 

5、运行:

这里运行看一下:

 

而如果想绘制三阶,也类似,使用Path的API既可,但是!!!如果三阶以上你也想绘制,怎么办?系统没有直接提供相关的API呀,所以接下来处理高阶的绘制问题。

四阶及更高阶贝塞尔曲线:

1、先在屏幕随机绘制5个点:

既然是四阶,肯定是需要五个点的,一个开始点,一个结束点,还有三个控制点,所以在正式绘制曲线之前,先将准备工作做到位,这块比较简单,直接给出代码了:

 

代码如下:

package com.cexo.beziermaker.test;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.Random;

/**
 * 绘制四阶或更高阶贝塞尔曲线,使用德卡斯特里奥算法(贝塞尔公式)实现
 */
public class BezierView2 extends View {

    //多控制点【第一个点和最后一个点则为开始与结束点】
    private ArrayList<PointF> controlPoints = null;
    private Paint paint, linePointPaint;

    public BezierView2(Context context) {
        this(context, null);

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(4);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.BLACK);

        linePointPaint = new Paint();
        linePointPaint.setAntiAlias(true);
        linePointPaint.setStrokeWidth(4);
        linePointPaint.setStyle(Paint.Style.STROKE);
        linePointPaint.setColor(Color.RED);

        controlPoints = new ArrayList<>();
        init();
    }

    private void init() {
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            int x = random.nextInt(600) + 100;
            int y = random.nextInt(600) + 100;
            PointF pointF = new PointF(x, y);
            controlPoints.add(pointF);
        }
    }

    public BezierView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public BezierView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, -1);
    }

    public BezierView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 控制点和控制点连线
        int size = controlPoints.size();
        PointF point;
        for (int i = 0; i < size; i++) {
            point = controlPoints.get(i);
            if (i > 0) {
                // 控制点连线
                canvas.drawLine(controlPoints.get(i - 1).x, controlPoints.get(i - 1).y, point.x, point.y,
                        linePointPaint);
            }
            // 控制点
            canvas.drawCircle(point.x, point.y, 12, linePointPaint);
        }
    }
}

运行看一下效果:

当然这个点是随机的,每次运行是不一样的。

2、德卡斯特里奥算法(贝塞尔公式)了解:

好,接下来的核心就是怎么来绘制贝塞尔曲线的问题了,这里则需要使用到一个比较“高深”的算法了,如小标题所示:德卡斯特里奥算法,百度一下它:

算法的目标:

在正式了解该算法之前,我们先从贝塞尔曲线绘制特点来分析一下,比如这个三阶贝塞尔曲线:

是不是最终其实就是降阶来进行处理的,如下:

 

而对于“德卡斯特里奥算法”而言,它的目的就是可以达到降阶的效果,其实也就是通过这个算法,最终能够算出降为一阶的绘制点:

有了绘制点,那么将所有绘制点连接起来,不就成了一个曲线了么,这就是该算法的目的之所在,另外该算法能实现N阶贝塞尔曲线,是一个通用的算法,所以首先先明白该算法的一个目的很重要

算法的逻辑:

关于它的算法逻辑,说实话是有一点难理解的,反正我是在网上找了一圈,貌似说得都有点生涩,如果不理解透,你在编写代码时肯定会懵,不信,我先贴出来之后利用此算法实现贝塞尔曲线的一个代码:

是不是在你不了解此算法的公式之前,这代码是完全理解不了的,仅仅只能把这个方法当一个工具方法来用,但是别人问你怎么实现时,可以丢一句:“代码如此,自己看吧”。

1、先理解如何在一个线段中获得具体比例的点:【这块有点小绕,但是必须理解透】

关于这个算法的逻辑分析可以参考这位大佬的:https://blog.csdn.net/venshine/article/details/51750906,基本上网上说的都是这篇的内容,我也是基于这篇来理解的,不过我也是反复看之后才有所理解,这里理解算法的核心得要理解透这句话才行:

因为,有一个式子,跟我提前贴的代码中的式子很像呀:

理解透了这个公式,那对于整个算法的核心就已经掌握了,这篇博主我看完之后其实还是有点懵懵的,最后理解到位是通过它:https://wenku.baidu.com/view/7ae91e260722192e4436f605.html

其实两篇思想一样,只是这篇语言组织上详细通俗一点,咱们就根据两位大佬的博客,带着自己的理解来挼一挼:

其中提到了“向量”这个词, 不知各位都对它还记得么,对我来说印象比较深刻,因为之前学了“线性代数”:

如完全没印象的,可以移步到这https://www.cnblogs.com/webor2006/p/14245895.html瞅一瞅,图中所示的应该还是比较简单的,道出了这个算法的核心思想就是来找到C这个点的位置:

其中为啥是“使得C分向量AB为u:1-u(即∣AC∣:∣AB∣= u)”,其实是将整个AB看成是1(why?),然后u是∣AC∣:∣AB∣,其中还知道“∣AC∣”是啥意思不?小学数学还是初中数学的知识了,表示AC两点的距离,复习下:

那:

那是不是:

所以很显然C就把向量AB分成了u:1-u了。接下来则需要来解释上面标红的一句话了:“整个AB看成是1”,这里回忆一下最开始对于一阶贝塞尔曲线的执行过程:

看到其中的t了么?最大就是1,因为曲线的绘制就是根据算出来的绘制点连线而成,而多少个绘制点则你可以自己来根据这个1进行拆分,可能这边说得有点绕,这里先把之后咱们实现时需要用到的一个代码先提前亮出来吧,这样就比较好理解了:

当然现在贴代码有点超前,不过细节先不用关注,这里只是为了理解这个“1”,接下来的核心就是理解如何算C这个绘制点了,也就是博主的这句话:

理解一下,“A到B的向量是B - A”,这个应该容易理解吧,就是指AB之间的距离嘛,然后C点占整个1的百分比是u,那么C点的位置很明显就是u * (B -A)嘛,这样就把C给算出来了呀,但是“考虑到A点的位置”,由于A点的位置不一定是原点,比如发生偏移之类的,那么整个C点的位置还得基于A的位置来算,所以整个式子就变成了“A + u(B - A)”,再根据结合率之类的,最终就可以变化成这个式子了:“(1 - u)*A + u*B”,其中是不是可以发现,只要我将u这个比例值知道了,整个C点的绘制位置就知道了?

2、理解递归的逻辑:

对于一个N阶贝塞尔曲线而言,你最终要绘制的只是最里面1阶贝塞尔曲线,所以光知道了,比如这样的一个五阶贝塞尔曲线的演变过程:

也就是在计算过程中会涉及到N多个点对吧,但是绘制而言其实只需要它就可以了:

很明显这里需要一个递归的过程最终再算出指定t下的贝塞尔曲线所在的那个绘制点对吧, 那递归的思路是啥呢?下面基于图中的这个场景来简单挼一下:

其中的u为0.4,也就是该点是整个贝塞尔曲线40%的位置,说实话,这个递归过程不是那么好描述,其实就是降级的过程,最终降到一阶,其绘制点就给算出来了,这块具体的过程,我打算先把代码实现贴出来之后,再结合程序来进行理解,这样可能更加容易理解一点,所以这里的递归过程先忽略。

3、实现四阶贝塞尔曲线:

接下来直接来实现多阶贝塞尔曲线了,代码其实还是比较亲切的,如下:

a、先将整个绘制分成1000等份,然后一等份一等份的算出绘制点:

而具体算法,目前还未实现:

b、完成绘制点的计算:

/**
     * deCasteljau算法
     * p(i,j) =  (1-t) * p(i-1,j)  +  u * p(i-1,j-1);
     *
     * @param i 阶数   4
     * @param j 控制点 3
     * @param t 时间
     * @return
     */
    private float deCasteljauX(int i, int j, float t) {
        if (i == 1) {
            return (1 - t) * controlPoints.get(j).x + t * controlPoints.get(j + 1).x;
        }
        return (1 - t) * deCasteljauX(i - 1, j, t) + t * deCasteljauX(i - 1, j + 1, t);
    }

    /**
     * deCasteljau算法
     *
     * @param i 阶数
     * @param j 点
     * @param t 时间
     * @return
     */
    private float deCasteljauY(int i, int j, float t) {
        if (i == 1) {
            return (1 - t) * controlPoints.get(j).y + t * controlPoints.get(j + 1).y;
        }
        return (1 - t) * deCasteljauY(i - 1, j, t) + t * deCasteljauY(i - 1, j + 1, t);
    }

整体的思路就是如果当前是1阶了,则直接根据德卡斯特里奥算法来算出绘制点,如果大于1阶的,则递归降级处理,具体的细节这里先不解释,因为下面会根据运行结果再来分析整个程序的执行过程的,这样你就明白了其递归的一个逻辑了。

4、运行:

接下来运行看一下,目前看的是4阶的效果,由于点是随机产生的,多运行几次,看曲线画得完不完美:

嗯,挺完美的~~ 

5、实现N阶贝塞尔曲线:

为了验证这种方式的通用性,用两个极端的阶数来测试一下,一个是2阶,一个是7阶,先来看一个2阶的吧,咱们把程序写一个数字既可,其它完全不需要动:

运行:

 

接下来再看一下7阶的,同样改个数字既可:

运行:

完美,可见通过这种算法来实现的贝塞尔曲线可以满足所有阶次。 

6、分析递归逻辑:

好!!最后这里还遗留一个问题,那就是整个递归的思路是咋样的呢?对应的代码是:

这里以三阶贝塞尔曲线绘制为例,来debug一下咱们的程序,为了方便分析,这里将随机的点改为定死的点,如下:

先看一下运行的样子:

注意,起始点和结束点的位置,因为关系到待会的逻辑分析涉及到的控制点的控制:

而为了方便分析流程,这里将t定死一个,只分析一个绘制点既可,如下:

目前长这样了:

而为了更加精简,只分析一下坐标点的x的值计算过程既可,因为y值的计算过程是一模一样的,也就是它:

另外,为了让分析代码变得清晰,这里将deCasteljauX调整一下:

是不是这样跟咱们理论所分析的公式就一模一样了:

这样分析递归也好分析一些,不然一行代码有两个递归看着有点晕,一切就绪,接下来进入代码分析阶段。

开始分析:

1、i=3,j=0:

其中i代表是阶数,j代表的是控制点,也就是首先是来计算三阶的这

由于阶数不是1,所以此时会进入递归环节。

2、递归计算A点:float A = deCasteljauX(i - 1, j, t);

在分析前,要明白目前分析的点是哪个,这里利用开源的BezierMaker来手动制造跟咱们分析的三阶贝塞尔曲线,因为它有降阶的辅助线供参考,所以对于想把贝塞尔曲线学好的强烈推荐这个工具,真的很方便,其实目前这句代码的目的就是为了算出它:

【注意】:这里我制造效果时有点顺序问题,应该p0在p3的位置的,由于在写逻辑时已经基于这样的图进行了,这里就说明一下,p3是第一个点,p0是最后一个点【正常p0是第一个点,p3是最后一个点嘛,犯了个细节错误,将错就错了,不过不影响整体的逻辑理解】,这个注意一下!!!

而这个A点的算出,很明显是需要根据降阶操作之后的这俩A,B点来算出,如下:

所以,为了降阶,这里将阶数减一再次递归了:

然后此时进入递归环节:

由于阶数目前还是不等于1,又会执行到这:

算二阶的A:

而此时由于阶数已经降为2了,再递归降一级,就变为1阶了,此时结果就直接可以算出返回了:

 

此时的A为619.23193,也就是这个点坐标的x值:

算二阶的B:

此时则开始算降阶的B点了:

同样由于阶数已经还是2,再递归降一级,就变为1阶了,此时结果就直接可以算出返回了:

此时的B为361.1466,也就是这个点坐标的x值:

结果得出:

此时再根据德卡斯特里奥算法,此时这个二阶上的点结果x值就出来了:

也就是这个点算出来为482.70563:

这里好好理会一下整个A值的计算过程,很明显的一个降级,也就是不管多少阶,最终都会递归降到1阶,然后再算出其中的A,B点的值,再算出最终的曲线绘制点,当然这个递归不是很好理解的,需要自己边画边挼一下才行。

3、计算B点:float B = deCasteljauX(i - 1, j + 1, t);

理清了A点的计算递归逻辑,这个B点的计算逻辑是一模一样的,同样在分析前,需要明确目标,这句代码的意思是算出它:

而这个B点的算出,很明显是需要根据降阶操作之后的这俩A,B点来算出,如下:

所以,为了降阶,这里将阶数减一再次递归了:

然后注意,此时的j控制点+1了:

这是因为此时B点的算出得根据第二个控制点来:

是不是这两个点得基于p2起步来算出?所以这里的控制点数+1了,这个细节需要好好体会。

然后此时进入递归环节:

由于阶数目前还是不等于1,又会执行到这:

 

算二阶的A:

而此时由于阶数已经降为2了,再递归降一级,就变为1阶了,此时结果就直接可以算出返回了:

此时的A为361.1466,也就是这个点坐标的x值:

 

算二阶的B:

此时则开始算降阶的B点了:

同样由于阶数已经还是2,再递归降一级,就变为1阶了,此时结果就直接可以算出返回了:

此时的B为213.16849,也就是这个点坐标的x值:

结果得出:

此时再根据德卡斯特里奥算法,此时这个二阶上的点结果x值就出来了:

也就是这个点算出来为282.86667:

4、根据德卡斯特里奥算法来算出结果:

目前A,B两点已经算出来了,你说要绘制的这个点怎么得出应该就很简单了吧?

 

这样,绘制点就成功算出:

总结递归逻辑:

经过这么完整的一个debug分析,是不是比直接分析代码要容易理解一些?对于递归的程序,如果当你光看代码不好理解的话,建议用debug的方式完整的梳理一遍,这样你就会比较容易理解,不过!!!整体还是比较晕的,这里最后再整理一下,其实递归的目的就是降级,降到1阶,递归的目的就是为了算出1阶的开始点和结束点:

有了这俩点,最终绘制点就可以根据德卡斯特里奥算法来算出了。

 7、理解等份的含义:

最后,咱们将debug时的一些代码给还原,最后再来看个东东,就是关于它的作用:

之前不是说这个数值越大其绘制出来的曲线就越细腻么,为了体现,咱们改为很小,反着看一下,是不是越小,越不细腻,这里先把1000的图贴出来:

然后咱们将其改为10,再对比看一下:

 

 

是不是对比非常明显,等份小了,很明显点与点之间的距离大了,然后连线当然就不圆润了啦,通过这个对比应该就明白这个等份的意义了吧?

总结:

至此,我们已经学会了N阶贝塞尔曲线的画法了,但是!!!在之后咱们实现的QQ汽泡的效果时只会使用到Android Path API来绘制二阶贝塞尔曲线的,那这么复杂的算法有何意义呢?可以让你对贝塞尔曲线的概念理解得更加深刻呀,如果你只学Android API和二三阶贝塞尔曲线的用法,人家都把实现细节给你封装好了,能知道贝塞尔曲线的精髓么?这篇耗了我业余大概一周多时间梳理吧,收获反正是挺大的,有种茅塞顿开的感觉,如果你有心,建议自己写一篇博客从头至尾的按自己的思路来梳理分享出来,哪怕是网上参考的【但是一定得要带着自己的理解去解读,而不是直接copy】,这样你就能体会到虽说时间成本比较大,但是你的收获也很大,这也是为啥我一直坚持的原因,这样可以让自己摒弃浮躁,脚踏实地的一步步朝前走。

本来计划还要实现一个qq汽泡的效果滴,不过在这个算法上花得篇幅较长,放下次吧。

posted on 2021-09-15 10:34  cexo  阅读(839)  评论(0编辑  收藏  举报

导航