貝塞爾曲線原理分析及其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里還有四階曲線的展示,可以看看,原理與二階三階雷同,就不貼代碼了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容