贝塞尔曲线原理分析及其Android的实现

本文主要内容为贝塞尔曲线原理解析并用 SurfaceView 实现其展示动画

关于SurfaceView 的使用,大家可以看我的上一篇文章 Android:SurfaceView 的使用(附代码模板)

概述
贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。贝塞尔曲线是计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝塞尔曲线工具,如PhotoShop等。在Flash4中还没有完整的曲线工具,而在Flash5里面已经提供出贝塞尔曲线工具。
曲线作用
由于用计算机画图大部分时间是操作鼠标来掌握线条的路径,与手绘的感觉和效果有很大的差别。即使是一位精明的画师能轻松绘出各种图形,拿到鼠标想随心所欲的画图也不是一件容易的事。这一点是计算机万万不能代替手工的工作,所以到目前为止人们只能颇感无奈。使用贝塞尔工具画图很大程度上弥补了这一缺憾。贝塞尔曲线是计算机图形图像造型的基本工具,是图形造型运用得最多的基本线条之一。

贝塞尔曲线

——来自百度百科

如果大家用过 XMind 软件或者是 WPS 软件来绘制思维导图的话,那么对下面的连线一定不会陌生。下图中如果我们要连接两个分支主题,就会用到基于三阶贝塞尔曲线的连线功能,如下所示。我们先确定需要被连接起来的起始点和终点,然后拖动两个控制点就可以任意地绘制一条连接曲线。通过这篇文章,我为大家讲解一下贝塞尔曲线的实现原理,看完本文,你也可以轻轻松松实现这个炫酷的贝塞尔曲线。


XMind 示例

贝塞尔曲线公式

以下公式中:B(t)为t时间下 点的坐标;P0为起点,Pn为终点,Pi为控制点

一阶贝塞尔曲线公式(线性公式)

给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。且其等同于线性插值。


一阶贝塞尔曲线

二阶贝塞尔曲线公式

设P0、P02、P2是一条曲线上顺序三个不同的点。过P0和P2点的两切线交于P1点,在P02点的切线交P0P1和P2P1于P01和P11,则如下比例成立:



当P0,P2固定,引入参数 t,令上述比值为 t:(1-t),即有:

将一式二式代入三式可得:

二阶贝塞尔曲线P02可以定义为分别由前两个顶点(P0,P1)和后两个顶点(P1,P2)决定的一阶贝塞尔曲线的线性组合。
可得出二阶贝塞尔曲线的公式:

三阶贝塞尔曲线公式

按照二阶的推导原理,以此类推,由四个控制点定义的三阶贝塞尔曲线P03可被定义为分别由(P0,P1,P2)和(P1,P2,P3)确定的两条二阶贝塞尔曲线的线性组合:P03 = (1-t)P02 + tP12

可递归代入得出三阶贝塞尔曲线公式:



现代的成象系统,如PostScript、Asymptote和Metafont,运用了以贝塞尔样条组成的三阶贝塞尔曲线,用来描绘曲线轮廓。

四阶及以上的贝塞尔曲线公式

由(n+1)个控制点 Pi(i=0,1,...,n) 定义的n阶贝塞尔曲线 P0n 可被定义为分别由前、后 n 个控制点定义的两条 (n-1) 阶贝塞尔曲线 P0n-1 与 P1n-1 的线性组合:


由此得到贝塞尔曲线的递推计算公式(通用公式):

其一般参数公式为:
n 阶贝塞尔曲线可如下推断。给定点P0、P1、…、Pn,其贝塞尔曲线即:

四阶贝塞尔曲线:


四阶贝塞尔曲线

五阶贝塞尔曲线:

五阶贝塞尔曲线
公式说明
  1. 开始于P0并结束于Pn的曲线,即所谓的端点插值法属性。
  1. 曲线是直线的充分必要条件是所有的控制点都位在曲线上。同样的,贝塞尔曲线是直线的充分必要条件是控制点共线。
  2. 曲线的起始点(结束点)相切于贝塞尔多边形的第一节(最后一节)。
  3. 一条曲线可在任意点切割成两条或任意多条子曲线,每一条子曲线仍是贝塞尔曲线。
  4. 一些看似简单的曲线(如圆)无法以贝塞尔曲线精确的描述,或分段成贝塞尔曲线(虽然当每个内部控制点对单位圆上的外部控制点水平或垂直的的距离为时,分成四段的贝塞尔曲线,可以小于千分之一的最大半径误差近似于圆)。
  5. 位于固定偏移量的曲线(来自给定的贝塞尔曲线),又称作偏移曲线(假平行于原来的曲线,如两条铁轨之间的偏移)无法以贝塞尔曲线精确的形成(某些琐屑实例除外)。无论如何,现存的启发法通常可为实际用途中给出近似值。

Android上实现贝赛尔曲线

在Android实现贝赛尔曲线,要借助android.graphics.Path,其中绘制贝赛尔曲线的方法在Api v1就已经提供了:

Path.moveTo(float x, float y) // Path的初始点
Path.lineTo(float x, float y) // 线性公式的贝赛尔曲线, 其实就是直线
Path.quadTo(float x1, float y1, float x2, float y2) // 二阶贝赛尔曲线
Path.cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) // 三阶贝赛尔曲线
...

这上面是Java层调用的代码,最终调用的是Skia库的一系列方法,Skia是一个C++2D向量图形处理函数库,感兴趣的可以继续深入研究研究。

实现二阶贝塞尔曲线

一、首先来看一下可操作的二阶贝塞尔曲线动态图:


二阶贝塞尔曲线

自定义 MyBezierCurveQuadratic 类代码如下,很简单:

package com.example.pc.mybeziercurveview.view;

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

/**
 * 二阶
 * Created by Deeson on 2017/5/24.
 */
public class MyBezierCurveQuadratic extends View{

    private Paint mPaint;
    private Path mPath;
    private int centerX,centerY;
    private PointF start,end,control;

    public MyBezierCurveQuadratic(Context context) {
        super(context);
        init();
    }

    public MyBezierCurveQuadratic(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyBezierCurveQuadratic(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPath = new Path();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);

        start = new PointF(0,0);
        end = new PointF(0,0);
        control = new PointF(0,0);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w/2;
        centerY = h/2;
        //初始化数据点和控制点的位置
        start.x = centerX - 200;
        start.y = centerY;
        end.x = centerX + 200;
        end.y = centerY;
        control.x = centerX;
        control.y = centerY - 100;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制数据点和控制点
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(20);
        canvas.drawPoint(start.x, start.y, mPaint);
        canvas.drawPoint(end.x, end.y, mPaint);
        canvas.drawPoint(control.x, control.y, mPaint);

        //绘制辅助线
        mPaint.setStrokeWidth(4);
        canvas.drawLine(start.x,start.y,control.x,control.y,mPaint);
        canvas.drawLine(control.x, control.y, end.x, end.y, mPaint);

        //绘制二阶贝塞尔曲线
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(8);
        mPath.reset();
        mPath.moveTo(start.x,start.y);
        mPath.quadTo(control.x, control.y, end.x, end.y);
        canvas.drawPath(mPath,mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                control.x = event.getX();
                control.y = event.getY();
                invalidate();
            break;
        }
        return true;
    }
}

二、接着来看,如何用 SurfaceView 实现如下曲线动画效果:

关于SurfaceView 的使用,大家可以看我的上一篇文章 Android:SurfaceView 的使用(附代码模板)

二阶贝塞尔曲线show

这里,最关键的代码是开启子线程绘制二阶贝塞尔曲线,如下:

//辅助线坐标点
process1.x = (1 - t) * start.x + t * control.x;
process1.y = (1 - t) * start.y + t * control.y;
process2.x = (1 - t) * control.x + t * end.x;
process2.y = (1 - t) * control.y + t * end.y;

//贝塞尔曲线通用函数
x = (1 - t) * process1.x + t * process2.x;
y = (1 - t) * process1.y + t * process2.y;

mPath.lineTo(x, y);

//绘制数据点和控制点
mCanvas.drawPoint(start.x, start.y, mPointPaint);
mCanvas.drawPoint(control.x, control.y, mPointPaint);
mCanvas.drawPoint(end.x, end.y, mPointPaint);
//绘制数据点和控制点的连线
mCanvas.drawLine(start.x, start.y, control.x, control.y, mLinePaint);
mCanvas.drawLine(control.x, control.y, end.x, end.y, mLinePaint);
//绘制辅助线和辅助点
mCanvas.drawLine(process1.x, process1.y, process2.x, process2.y, mAssistLinePaint);
mCanvas.drawPoint(process1.x,process1.y,mAssistPointPaint);
mCanvas.drawPoint(process2.x,process2.y,mAssistPointPaint);
//绘制二阶贝塞尔曲线的当前点
mCanvas.drawPoint(x, y, mPointPaint);
//绘制二阶贝塞尔曲线
mCanvas.drawPath(mPath, mPaint);

自定义 QuadraticBezierShowView 类的完整代码如下:

package com.example.pc.mybeziercurveview.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * 二阶
 * Created by Deeson on 2017/5/24.
 */
public class QuadraticBezierShowView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    //分别对应贝塞尔曲线、点、数据点和控制点之间的线、辅助线、辅助点
    private Paint mPaint, mPointPaint, mLinePaint, mAssistLinePaint,mAssistPointPaint;
    //绘制贝塞尔曲线的path
    private Path mPath;
    //布局的中心点
    private int centerX, centerY;
    //分别对应贝塞尔曲线的起点、终点、控制点、辅助线的起点、终点
    private PointF start, end, control, process1, process2;

    private SurfaceHolder mHolder;
    //用于绘图的canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;

    float x = 0;//贝塞尔曲线的实时点x坐标
    float y = 0;//贝塞尔曲线的实时点y坐标
    float t = 0;//实施进度,0<=t<=1

    public QuadraticBezierShowView(Context context) {
        super(context);
        init();
    }

    public QuadraticBezierShowView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public QuadraticBezierShowView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPath = new Path();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(5);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);

        mPointPaint = new Paint();
        mPointPaint.setColor(Color.BLACK);
        mPointPaint.setStrokeWidth(10);
        mPointPaint.setStyle(Paint.Style.STROKE);

        mLinePaint = new Paint();
        mLinePaint.setColor(Color.GRAY);
        mLinePaint.setStrokeWidth(4);
        mLinePaint.setStyle(Paint.Style.STROKE);

        mAssistLinePaint = new Paint();
        mAssistLinePaint.setColor(Color.GREEN);
        mAssistLinePaint.setStrokeWidth(4);
        mAssistLinePaint.setStyle(Paint.Style.STROKE);

        mAssistPointPaint = new Paint();
        mAssistPointPaint.setColor(Color.GREEN);
        mAssistPointPaint.setStrokeWidth(10);
        mAssistPointPaint.setStyle(Paint.Style.FILL);

        start = new PointF(0, 0);
        end = new PointF(0, 0);
        control = new PointF(0, 0);
        process1 = new PointF(0, 0);
        process2 = new PointF(0, 0);

        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        //初始化数据点和控制点的位置
        start.x = centerX - 200;
        start.y = centerY;
        end.x = centerX + 200;
        end.y = centerY;
        control.x = centerX - 50;
        control.y = centerY - 300;
        x = start.x;
        y = start.y;
        mPath.moveTo(x, y);
    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {
        while (mIsDrawing) {
            draw();
            if (t <= 1) {
                t += 0.003;

                //辅助线坐标点
                process1.x = (1 - t) * start.x + t * control.x;
                process1.y = (1 - t) * start.y + t * control.y;
                process2.x = (1 - t) * control.x + t * end.x;
                process2.y = (1 - t) * control.y + t * end.y;

                //贝塞尔曲线通用函数
                x = (1 - t) * process1.x + t * process2.x;
                y = (1 - t) * process1.y + t * process2.y;

                //二阶贝塞尔曲线函数
//                x = (float) (Math.pow((1 - t), 2) * start.x + 2 * t * (1 - t) * control.x + Math.pow(t, 2) * end.x);
//                y = (float) (Math.pow((1 - t), 2) * start.y + 2 * t * (1 - t) * control.y + Math.pow(t, 2) * end.y);

                mPath.lineTo(x, y);
            } else {
                mIsDrawing = false;
            }

        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            //绘制数据点和控制点
            mCanvas.drawPoint(start.x, start.y, mPointPaint);
            mCanvas.drawPoint(control.x, control.y, mPointPaint);
            mCanvas.drawPoint(end.x, end.y, mPointPaint);
            //绘制数据点和控制点的连线
            mCanvas.drawLine(start.x, start.y, control.x, control.y, mLinePaint);
            mCanvas.drawLine(control.x, control.y, end.x, end.y, mLinePaint);
            //绘制辅助线和辅助点
            mCanvas.drawLine(process1.x, process1.y, process2.x, process2.y, mAssistLinePaint);
            mCanvas.drawPoint(process1.x,process1.y,mAssistPointPaint);
            mCanvas.drawPoint(process2.x,process2.y,mAssistPointPaint);
            //绘制二阶贝塞尔曲线的当前点
            mCanvas.drawPoint(x, y, mPointPaint);
            //绘制二阶贝塞尔曲线
            mCanvas.drawPath(mPath, mPaint);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != mCanvas) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }
}
实现三阶贝塞尔曲线

一、同样的,先看看可操作的三阶贝塞尔曲线:


三阶贝塞尔曲线

自定义 MyBezierCurveCubic 类的完整代码如下:

package com.example.pc.mybeziercurveview.view;

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

/**
 * 三阶
 * Created by Deeson on 2017/5/24.
 */
public class MyBezierCurveCubic extends View{

    private Paint mPaint;
    private Path mPath;
    private int centerX,centerY;
    private PointF start,end,control1,control2;
    public static final int CONTROL_ONE = 0;
    public static final int CONTROL_TWO = 1;
    private int control = CONTROL_ONE;

    public MyBezierCurveCubic(Context context) {
        super(context);
        init();
    }

    public MyBezierCurveCubic(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyBezierCurveCubic(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPath = new Path();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        start = new PointF(0,0);
        end = new PointF(0,0);
        control1 = new PointF(0,0);
        control2 = new PointF(0,0);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w/2;
        centerY = h/2;

        //初始化数据点和控制点
        start.x = centerX - 200;
        start.y = centerY;
        end.x = centerX + 200;
        end.y = centerY;
        control1.x = centerX - 200;
        control1.y = centerY - 200;
        control2.x = centerX + 200;
        control2.y = centerY + 200;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制数据点和控制点
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(20);
        canvas.drawPoint(start.x, start.y, mPaint);
        canvas.drawPoint(end.x,end.y,mPaint);
        canvas.drawPoint(control1.x,control1.y,mPaint);
        canvas.drawPoint(control2.x, control2.y, mPaint);
        //绘制辅助线
        mPaint.setStrokeWidth(4);
        canvas.drawLine(start.x, start.y, control1.x, control1.y, mPaint);
        canvas.drawLine(control1.x, control1.y, control2.x, control2.y, mPaint);
        canvas.drawLine(control2.x, control2.y, end.x, end.y, mPaint);
        //绘制三阶贝塞尔曲线
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(8);
        mPath.reset();
        mPath.moveTo(start.x,start.y);
        mPath.cubicTo(control1.x,control1.y,control2.x,control2.y,end.x,end.y);
        canvas.drawPath(mPath,mPaint);
    }

    public void setControl(int control){
        this.control = control;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                if(control == CONTROL_ONE){
                    control1.x = event.getX();
                    control1.y = event.getY();
                }else{
                    control2.x = event.getX();
                    control2.y = event.getY();
                }
                invalidate();
            break;
        }
        return true;
    }
}

二、接着来看,如何用 SurfaceView 实现如下三阶贝塞尔曲线动画效果:

关于SurfaceView 的使用,大家可以看我的上一篇文章 Android:SurfaceView 的使用(附代码模板)

三阶贝塞尔曲线show

自定义 CubicBezierShowView 类的完整代码如下:

package com.example.pc.mybeziercurveview.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * 三阶
 * Created by Deeson on 2016/7/12.
 */
public class CubicBezierShowView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    //分别对应贝塞尔曲线、点、数据点和控制点之间的线、第一层辅助线、第一层辅助点、第二层辅助线、第二层辅助点
    private Paint mPaint, mPointPaint, mLinePaint, mAssistLine1Paint, mAssistPoint1Paint, mAssistLine2Paint, mAssistPoint2Paint;
    //绘制贝塞尔曲线的path
    private Path mPath;
    //布局的中心点
    private int centerX, centerY;
    //分别对应三阶贝塞尔曲线的起点、终点、控制点
    private PointF start, end, control1, control2;
    //第一层辅助线的3个端点(相当于动态的二阶贝塞尔曲线的起点,控制点,终点)
    private PointF process1, process2, process3;
    //第二层辅助线的起点和终点
    private PointF secondProcess1, secondProcess2;

    private SurfaceHolder mHolder;
    //用于绘图的canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;

    float x = 0;//贝塞尔曲线的实时点x坐标
    float y = 0;//贝塞尔曲线的实时点y坐标
    float t = 0;//实施进度,0<=t<=1

    public CubicBezierShowView(Context context) {
        super(context);
        init();
    }

    public CubicBezierShowView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CubicBezierShowView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //贝塞尔曲线
        mPaint = new Paint();
        mPath = new Path();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(5);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);

        //点
        mPointPaint = new Paint();
        mPointPaint.setColor(Color.BLACK);
        mPointPaint.setStrokeWidth(10);
        mPointPaint.setStyle(Paint.Style.STROKE);

        //数据点和控制点的连线
        mLinePaint = new Paint();
        mLinePaint.setColor(Color.GRAY);
        mLinePaint.setStrokeWidth(4);
        mLinePaint.setStyle(Paint.Style.STROKE);

        //第一层辅助线
        mAssistLine1Paint = new Paint();
        mAssistLine1Paint.setColor(Color.GREEN);
        mAssistLine1Paint.setStrokeWidth(4);
        mAssistLine1Paint.setStyle(Paint.Style.STROKE);

        //第一层辅助点
        mAssistPoint1Paint = new Paint();
        mAssistPoint1Paint.setColor(Color.GREEN);
        mAssistPoint1Paint.setStrokeWidth(10);
        mAssistPoint1Paint.setStyle(Paint.Style.FILL);

        //第二层辅助线
        mAssistLine2Paint = new Paint();
        mAssistLine2Paint.setColor(Color.BLUE);
        mAssistLine2Paint.setStrokeWidth(4);
        mAssistLine2Paint.setStyle(Paint.Style.STROKE);

        //第二层辅助线
        mAssistPoint2Paint = new Paint();
        mAssistPoint2Paint.setColor(Color.BLUE);
        mAssistPoint2Paint.setStrokeWidth(10);
        mAssistPoint2Paint.setStyle(Paint.Style.FILL);

        //三阶贝塞尔曲线的起点终点
        start = new PointF(0, 0);
        end = new PointF(0, 0);
        //三阶贝塞尔曲线的两个控制点
        control1 = new PointF(0, 0);
        control2 = new PointF(0, 0);
        //第一层辅助线的三个端点
        process1 = new PointF(0, 0);
        process2 = new PointF(0, 0);
        process3 = new PointF(0, 0);
        //第二层辅助线的两个端点
        secondProcess1 = new PointF(0, 0);
        secondProcess2 = new PointF(0, 0);

        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        //初始化数据点和控制点的位置
        start.x = centerX - 200;
        start.y = centerY;
        end.x = centerX + 200;
        end.y = centerY;
        control1.x = centerX - 150;
        control1.y = centerY - 300;
        control2.x = centerX + 170;
        control2.y = centerY - 340;
        x = start.x;
        y = start.y;
        mPath.moveTo(x, y);

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {
        while (mIsDrawing) {
            draw();
            if (t <= 1) {
                t += 0.003;
                //重点在这里
                bezierDraw();

                mPath.lineTo(x, y);
            } else {
                mIsDrawing = false;
            }

        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            //绘制数据点和控制点
            mCanvas.drawPoint(start.x, start.y, mPointPaint);
            mCanvas.drawPoint(control1.x, control1.y, mPointPaint);
            mCanvas.drawPoint(control2.x, control2.y, mPointPaint);
            mCanvas.drawPoint(end.x, end.y, mPointPaint);
            //绘制数据点和控制点的连线
            mCanvas.drawLine(start.x, start.y, control1.x, control1.y, mLinePaint);
            mCanvas.drawLine(control1.x, control1.y, control2.x, control2.y, mLinePaint);
            mCanvas.drawLine(control2.x, control2.y, end.x, end.y, mLinePaint);
            //绘制第一层辅助线和辅助点
            mCanvas.drawLine(process1.x, process1.y, process2.x, process2.y, mAssistLine1Paint);
            mCanvas.drawLine(process2.x, process2.y, process3.x, process3.y, mAssistLine1Paint);
            mCanvas.drawPoint(process1.x, process1.y, mAssistPoint1Paint);
            mCanvas.drawPoint(process2.x, process2.y, mAssistPoint1Paint);
            mCanvas.drawPoint(process3.x, process3.y, mAssistPoint1Paint);
            //绘制第二层辅助线和辅助点
            mCanvas.drawLine(secondProcess1.x, secondProcess1.y, secondProcess2.x, secondProcess2.y, mAssistLine2Paint);
            mCanvas.drawPoint(secondProcess1.x, secondProcess1.y, mAssistPoint2Paint);
            mCanvas.drawPoint(secondProcess2.x, secondProcess2.y, mAssistPoint2Paint);
            //绘制三阶贝塞尔曲线的当前点
            mCanvas.drawPoint(x, y, mPointPaint);
            //绘制三阶贝塞尔曲线
            mCanvas.drawPath(mPath, mPaint);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != mCanvas) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }

    private void bezierDraw() {
        //第一层辅助线坐标点
        process1.x = (1 - t) * start.x + t * control1.x;
        process1.y = (1 - t) * start.y + t * control1.y;
        process2.x = (1 - t) * control1.x + t * control2.x;
        process2.y = (1 - t) * control1.y + t * control2.y;
        process3.x = (1 - t) * control2.x + t * end.x;
        process3.y = (1 - t) * control2.y + t * end.y;
        //第二层辅助线坐标点
        secondProcess1.x = (1 - t) * process1.x + t * process2.x;
        secondProcess1.y = (1 - t) * process1.y + t * process2.y;
        secondProcess2.x = (1 - t) * process2.x + t * process3.x;
        secondProcess2.y = (1 - t) * process2.y + t * process3.y;

        //贝塞尔曲线通用公式
        x = (1 - t) * secondProcess1.x + t * secondProcess2.x;
        y = (1 - t) * secondProcess1.y + t * secondProcess2.y;

        //三阶贝塞尔曲线函数
//        x = (float) (Math.pow((1 - t), 3) * start.x + 3 * t * Math.pow((1 - t), 2) * control1.x + 3 * Math.pow(t, 2) * (1 - t) * control2.x + Math.pow(t, 3) * end.x);
//        y = (float) (Math.pow((1 - t), 3) * start.y + 3 * t * Math.pow((1 - t), 2) * control1.y + 3 * Math.pow(t, 2) * (1 - t) * control2.y + Math.pow(t, 3) * end.y);
    }
}
最后

Demo下载地址 点这里,Demo里还有四阶曲线的展示,可以看看,原理与二阶三阶雷同,就不贴代码了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容