貝塞爾曲線原理分析及其Android的實現(xiàn)

本文主要內(nèi)容為貝塞爾曲線原理解析并用 SurfaceView 實現(xiàn)其展示動畫

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

概述
貝塞爾曲線(Bézier curve)乏屯,又稱貝茲曲線或貝濟埃曲線,是應用于二維圖形應用程序的數(shù)學曲線瑞凑。一般的矢量圖形軟件通過它來精確畫出曲線扼褪,貝茲曲線由線段與節(jié)點組成砂碉,節(jié)點是可拖動的支點,線段像可伸縮的皮筋旬蟋,我們在繪圖工具上看到的鋼筆工具就是來做這種矢量曲線的。貝塞爾曲線是計算機圖形學中相當重要的參數(shù)曲線,在一些比較成熟的位圖軟件中也有貝塞爾曲線工具技掏,如PhotoShop等幔崖。在Flash4中還沒有完整的曲線工具滋迈,而在Flash5里面已經(jīng)提供出貝塞爾曲線工具罪郊。
曲線作用
由于用計算機畫圖大部分時間是操作鼠標來掌握線條的路徑,與手繪的感覺和效果有很大的差別扣囊。即使是一位精明的畫師能輕松繪出各種圖形乎折,拿到鼠標想隨心所欲的畫圖也不是一件容易的事。這一點是計算機萬萬不能代替手工的工作侵歇,所以到目前為止人們只能頗感無奈骂澄。使用貝塞爾工具畫圖很大程度上彌補了這一缺憾。貝塞爾曲線是計算機圖形圖像造型的基本工具惕虑,是圖形造型運用得最多的基本線條之一坟冲。

貝塞爾曲線

——來自百度百科

如果大家用過 XMind 軟件或者是 WPS 軟件來繪制思維導圖的話,那么對下面的連線一定不會陌生溃蔫。下圖中如果我們要連接兩個分支主題健提,就會用到基于三階貝塞爾曲線的連線功能,如下所示酒唉。我們先確定需要被連接起來的起始點和終點矩桂,然后拖動兩個控制點就可以任意地繪制一條連接曲線沸移。通過這篇文章痪伦,我為大家講解一下貝塞爾曲線的實現(xiàn)原理侄榴,看完本文,你也可以輕輕松松實現(xiàn)這個炫酷的貝塞爾曲線网沾。


XMind 示例

貝塞爾曲線公式

以下公式中:B(t)為t時間下 點的坐標癞蚕;P0為起點,Pn為終點,Pi為控制點

一階貝塞爾曲線公式(線性公式)

給定點P0、P1辉哥,線性貝茲曲線只是一條兩點之間的直線桦山。且其等同于線性插值。


一階貝塞爾曲線

二階貝塞爾曲線公式

設P0醋旦、P02恒水、P2是一條曲線上順序三個不同的點。過P0和P2點的兩切線交于P1點饲齐,在P02點的切線交P0P1和P2P1于P01和P11钉凌,則如下比例成立:



當P0,P2固定捂人,引入?yún)?shù) t御雕,令上述比值為 t:(1-t),即有:

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

二階貝塞爾曲線P02可以定義為分別由前兩個頂點(P0,P1)和后兩個頂點(P1,P2)決定的一階貝塞爾曲線的線性組合滥搭。
可得出二階貝塞爾曲線的公式:

三階貝塞爾曲線公式

按照二階的推導原理酸纲,以此類推,由四個控制點定義的三階貝塞爾曲線P03可被定義為分別由(P0瑟匆,P1闽坡,P2)和(P1,P2愁溜,P3)確定的兩條二階貝塞爾曲線的線性組合:P03 = (1-t)P02 + tP12

可遞歸代入得出三階貝塞爾曲線公式:



現(xiàn)代的成象系統(tǒng)无午,如PostScript、Asymptote和Metafont祝谚,運用了以貝塞爾樣條組成的三階貝塞爾曲線宪迟,用來描繪曲線輪廓。

四階及以上的貝塞爾曲線公式

由(n+1)個控制點 Pi(i=0,1,...,n) 定義的n階貝塞爾曲線 P0n 可被定義為分別由前交惯、后 n 個控制點定義的兩條 (n-1) 階貝塞爾曲線 P0n-1 與 P1n-1 的線性組合:


由此得到貝塞爾曲線的遞推計算公式(通用公式):

其一般參數(shù)公式為:
n 階貝塞爾曲線可如下推斷次泽。給定點P0、P1席爽、…意荤、Pn,其貝塞爾曲線即:

四階貝塞爾曲線:


四階貝塞爾曲線

五階貝塞爾曲線:

五階貝塞爾曲線
公式說明
  1. 開始于P0并結束于Pn的曲線只锻,即所謂的端點插值法屬性玖像。
  1. 曲線是直線的充分必要條件是所有的控制點都位在曲線上。同樣的齐饮,貝塞爾曲線是直線的充分必要條件是控制點共線捐寥。
  2. 曲線的起始點(結束點)相切于貝塞爾多邊形的第一節(jié)(最后一節(jié))笤昨。
  3. 一條曲線可在任意點切割成兩條或任意多條子曲線,每一條子曲線仍是貝塞爾曲線握恳。
  4. 一些看似簡單的曲線(如圓)無法以貝塞爾曲線精確的描述瞒窒,或分段成貝塞爾曲線(雖然當每個內(nèi)部控制點對單位圓上的外部控制點水平或垂直的的距離為時,分成四段的貝塞爾曲線乡洼,可以小于千分之一的最大半徑誤差近似于圓)崇裁。
  5. 位于固定偏移量的曲線(來自給定的貝塞爾曲線),又稱作偏移曲線(假平行于原來的曲線束昵,如兩條鐵軌之間的偏移)無法以貝塞爾曲線精確的形成(某些瑣屑實例除外)拔稳。無論如何,現(xiàn)存的啟發(fā)法通城鲁可為實際用途中給出近似值壳炎。

Android上實現(xiàn)貝賽爾曲線

在Android實現(xiàn)貝賽爾曲線,要借助android.graphics.Path逼侦,其中繪制貝賽爾曲線的方法在Api v1就已經(jīng)提供了:

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層調(diào)用的代碼匿辩,最終調(diào)用的是Skia庫的一系列方法,Skia是一個C++2D向量圖形處理函數(shù)庫榛丢,感興趣的可以繼續(xù)深入研究研究铲球。

實現(xiàn)二階貝塞爾曲線

一、首先來看一下可操作的二階貝塞爾曲線動態(tài)圖:


二階貝塞爾曲線

自定義 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;
        //初始化數(shù)據(jù)點和控制點的位置
        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);
        //繪制數(shù)據(jù)點和控制點
        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 實現(xiàn)如下曲線動畫效果:

關于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;

//貝塞爾曲線通用函數(shù)
x = (1 - t) * process1.x + t * process2.x;
y = (1 - t) * process1.y + t * process2.y;

mPath.lineTo(x, y);

//繪制數(shù)據(jù)點和控制點
mCanvas.drawPoint(start.x, start.y, mPointPaint);
mCanvas.drawPoint(control.x, control.y, mPointPaint);
mCanvas.drawPoint(end.x, end.y, mPointPaint);
//繪制數(shù)據(jù)點和控制點的連線
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 {

    //分別對應貝塞爾曲線戏挡、點芍瑞、數(shù)據(jù)點和控制點之間的線、輔助線褐墅、輔助點
    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;
        //初始化數(shù)據(jù)點和控制點的位置
        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;

                //貝塞爾曲線通用函數(shù)
                x = (1 - t) * process1.x + t * process2.x;
                y = (1 - t) * process1.y + t * process2.y;

                //二階貝塞爾曲線函數(shù)
//                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);
            //繪制數(shù)據(jù)點和控制點
            mCanvas.drawPoint(start.x, start.y, mPointPaint);
            mCanvas.drawPoint(control.x, control.y, mPointPaint);
            mCanvas.drawPoint(end.x, end.y, mPointPaint);
            //繪制數(shù)據(jù)點和控制點的連線
            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);
            }
        }
    }
}
實現(xiàn)三階貝塞爾曲線

一逝钥、同樣的屑那,先看看可操作的三階貝塞爾曲線:


三階貝塞爾曲線

自定義 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;

        //初始化數(shù)據(jù)點和控制點
        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);
        //繪制數(shù)據(jù)點和控制點
        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 實現(xiàn)如下三階貝塞爾曲線動畫效果:

關于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 {

    //分別對應貝塞爾曲線沃琅、點、數(shù)據(jù)點和控制點之間的線选酗、第一層輔助線阵难、第一層輔助點岳枷、第二層輔助線芒填、第二層輔助點
    private Paint mPaint, mPointPaint, mLinePaint, mAssistLine1Paint, mAssistPoint1Paint, mAssistLine2Paint, mAssistPoint2Paint;
    //繪制貝塞爾曲線的path
    private Path mPath;
    //布局的中心點
    private int centerX, centerY;
    //分別對應三階貝塞爾曲線的起點、終點空繁、控制點
    private PointF start, end, control1, control2;
    //第一層輔助線的3個端點(相當于動態(tài)的二階貝塞爾曲線的起點殿衰,控制點,終點)
    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);

        //數(shù)據(jù)點和控制點的連線
        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;
        //初始化數(shù)據(jù)點和控制點的位置
        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);
            //繪制數(shù)據(jù)點和控制點
            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);
            //繪制數(shù)據(jù)點和控制點的連線
            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;

        //三階貝塞爾曲線函數(shù)
//        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里還有四階曲線的展示,可以看看傲诵,原理與二階三階雷同凯砍,就不貼代碼了。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拴竹,一起剝皮案震驚了整個濱河市悟衩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌栓拜,老刑警劉巖座泳,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異幕与,居然都是意外死亡挑势,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門啦鸣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潮饱,“玉大人,你說我怎么就攤上這事诫给”荩” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵蝙搔,是天一觀的道長缕溉。 經(jīng)常有香客問我,道長吃型,這世上最難降的妖魔是什么证鸥? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上枉层,老公的妹妹穿的比我還像新娘泉褐。我一直安慰自己,他們只是感情好鸟蜡,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布膜赃。 她就那樣靜靜地躺著,像睡著了一般揉忘。 火紅的嫁衣襯著肌膚如雪跳座。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天泣矛,我揣著相機與錄音疲眷,去河邊找鬼。 笑死您朽,一個胖子當著我的面吹牛狂丝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哗总,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼几颜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了讯屈?” 一聲冷哼從身側響起蛋哭,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耻煤,沒想到半個月后具壮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡哈蝇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年棺妓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炮赦。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡怜跑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吠勘,到底是詐尸還是另有隱情性芬,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布剧防,位于F島的核電站植锉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏峭拘。R本人自食惡果不足惜俊庇,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一狮暑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辉饱,春花似錦搬男、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姓惑,卻和暖如春褐奴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挺益。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工歉糜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乘寒,地道東北人望众。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像伞辛,于是被迫代替她去往敵國和親烂翰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容