【android UI學習】貝塞爾曲線

簡介

Bézier curve(貝塞爾曲線)是應用於二維圖形應用程序的數學曲線。 曲線定義:起始點、終止點、控制點。通過調整控制點,貝塞爾曲線的形狀會發生變化

一階貝賽爾:

一階

一階貝塞爾曲線是一條直線,計算公式爲: 

一階計算公式

 

 二階貝賽爾:

1.步驟一:在平面內選3個不同線的點並且依次用線段連接。如下所示..

 2.步驟二:在AB和BC線段上找出點D和點E,使得 AD/AB = BE/BC

 3.步驟三:連接DE,在DE上尋找點F,F點需要滿足:DF/DE = AD/AB = BE/BC

 

4.步驟四:當D點從A到B,並且E點從B到C這條線路中所有的F點形成的線路,就是這條二階貝塞爾曲線

 

計算公式如下 

二階計算公式

 三階貝塞爾:

1.步驟一:在平面內選4個不同線的點並且依次用線段連接。如下所示..

 

步驟二:分別找到E F G三個點,滿足要求:AE/AB = BF/BC = CG/CD;在EH FG上找到H J點,滿足EH/EF = FI/FG = HJ/HI;

最終達到AE/AB = BF/BC = CG/CD=EH/EF = FI/FG = HJ/HI

  步驟三:這個J點所形成的線,就是我們的三階貝塞爾曲線:

計算公式如下:

 

三階計算公式

 高階貝塞爾曲線:

四階貝塞爾曲線:

四階

五階貝塞爾曲線

五階

 n階貝賽爾曲線計算公式如下:

N階計算公式

 貝塞爾曲線的代碼實現

  • 使用path自帶的方法畫

1:使用quadTo畫二階貝塞爾曲線

quadTo方法傳入兩個參數,第一個是二階貝塞爾曲線的控制點,第二個是二階貝塞爾曲線的結束點

        //二階貝塞爾曲線
        mPath1.moveTo(100, 100);//起始點
        mPath1.quadTo(400, 200, 10, 500);//二階貝塞爾曲線
        //繪製三個點
        canvas.drawCircle(100, 100, 10, mPaint);//起始點
        canvas.drawCircle(400, 200, 10, mPaint);//控制點
        canvas.drawCircle(10, 500, 10, mPaint);//結束點
        canvas.drawPath(mPath1, mPaint);

 繪製結果如下:

 2:使用cubicTo繪製三階貝塞爾曲線

cubicTo方法傳入參數分別爲第一個控制點,第二個控制點,結束點

        //三階貝塞爾曲線
        mPath1.moveTo(100, 100);
        mPath1.cubicTo(400, 200, 10, 500, 300, 700);

        canvas.drawCircle(100, 100, 10, mPaint);//起始點
        canvas.drawCircle(400, 200, 10, mPaint);//控制點
        canvas.drawCircle(120, 500, 10, mPaint);//控制點
        canvas.drawCircle(300, 700, 10, mPaint);//結束點


        canvas.drawPath(mPath1, mPaint);

繪製結果如下

 

  • 使用遞歸方式畫

  因爲path只提供了二階,三階的繪製方法,所以,高階貝塞爾曲線,就需要自己繪製了,我們這裏使用遞歸來繪製

遞歸方法主要如下遞歸計算每個點的座標值

    /**
     * p(i,j) =  (1-t) * p(i-1,j)  +  t * p(i-1,j+1);
     *
     * 遞歸計算各個點
     * @param i          階數
     * @param j          控制點
     * @param t          時間
     * @param calculateX  true代表計算的是x的值,false代表的是y的值
     * @return
     */
    private float deCastelJau(int i, int j, float t, boolean calculateX) {
        if (i == 1) {
            return calculateX ? (1 - t) * mControlPoints.get(j).x + t * mControlPoints.get(j + 1).x    :    (1 - t) * mControlPoints.get(j).y + t * mControlPoints.get(j + 1).y;
        } else {
            return (1 - t) * deCastelJau(i - 1, j, t, calculateX) + t * deCastelJau(i - 1, j + 1, t, calculateX);
        }
    }

 

完整方法如下

public class BezierView extends View {
    private Paint mPaint;
    private Paint mLinePointPaint;
    private Path mPath;
    private List<PointF> mControlPoints;//控制點集合(包含數據點)

    public BezierView(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(4);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.RED);
        mLinePointPaint = new Paint();
        mLinePointPaint.setAntiAlias(true);
        mLinePointPaint.setStrokeWidth(4);
        mLinePointPaint.setStyle(Paint.Style.STROKE);
        mLinePointPaint.setColor(Color.GRAY);
        mPath = new Path();
        mControlPoints = new ArrayList<>();
        init();
    }

    private void init() {
        mControlPoints.clear();
        //添加起始點
        PointF pointF = new PointF(200,200);
        //添加控制點
        PointF pointF1 = new PointF(1000,550);
        PointF pointF2 = new PointF(600,1150);
        //結束點
        PointF pointF3 = new PointF(1200,1350);
            mControlPoints.add(pointF);
            mControlPoints.add(pointF1);
            mControlPoints.add(pointF2);
            mControlPoints.add(pointF3);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //控制點及控制點的連線
        int size = mControlPoints.size();
        PointF pointF;
        for (int i = 0; i < size; i++) {
            pointF = mControlPoints.get(i);
            canvas.drawCircle(pointF.x, pointF.y, 10, mLinePointPaint);
        }
        //畫曲線
        buildBezierPoints();
        canvas.drawPath(mPath, mPaint);
    }

    /**
     * 劃線
     */
    private void buildBezierPoints() {
        mPath.reset();
        int order = mControlPoints.size() - 1;//階數
        //份數
        float delta = 1.0f / 1000;
        //將一條線分爲了1000個點來顯示,這裏的數量可以自己控制,越多越精確
        for (float t = 0; t <= 1; t += delta) {
            //bezier點集
            PointF pointF = new PointF(deCastelJau(order, 0, t, true), deCastelJau(order, 0, t, false));//計算在曲線上點位置
            if (t ==0) {
                mPath.moveTo(pointF.x, pointF.y);
            } else {
                mPath.lineTo(pointF.x, pointF.y);
            }
        }
    }
    /**
     * p(i,j) =  (1-t) * p(i-1,j)  +  t * p(i-1,j+1);
     *
     * 遞歸計算各個點
     * @param i          階數
     * @param j          控制點
     * @param t          時間
     * @param calculateX  true代表計算的是x的值,false代表的是y的值
     * @return
     */
    private float deCastelJau(int i, int j, float t, boolean calculateX) {
        if (i == 1) {
            return calculateX ? (1 - t) * mControlPoints.get(j).x + t * mControlPoints.get(j + 1).x    :    (1 - t) * mControlPoints.get(j).y + t * mControlPoints.get(j + 1).y;
        } else {
            return (1 - t) * deCastelJau(i - 1, j, t, calculateX) + t * deCastelJau(i - 1, j + 1, t, calculateX);
        }
    }

}

我們這裏只添加了四個點 所以上面的代碼運行結果如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章