1. 程式人生 > >貝塞爾曲線開發的藝術

貝塞爾曲線開發的藝術

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

                       

貝塞爾曲線開發的藝術

一句話概括貝塞爾曲線:將任意一條曲線轉化為精確的數學公式。

很多繪圖工具中的鋼筆工具,就是典型的貝塞爾曲線的應用,這裡的一個網站可以線上模擬鋼筆工具的使用:

http://bezier.method.ac/

這裡寫圖片描述

貝塞爾曲線中有一些比較關鍵的名詞,解釋如下:

  • 資料點:通常指一條路徑的起始點和終止點
  • 控制點:控制點決定了一條路徑的彎曲軌跡,根據控制點的個數,貝塞爾曲線被分為一階貝塞爾曲線(0個控制點)、二階貝塞爾曲線(1個控制點)、三階貝塞爾曲線(2個控制點)等等。

要想對貝塞爾曲線有一個比較好的認識,可以參考WIKI上的連結:

https://en.wikipedia.org/wiki/B%C3%A9zier_curve

這裡寫圖片描述

貝塞爾曲線模擬

在Android中,一般來說,開發者只考慮二階貝塞爾曲線和三階貝塞爾曲線,SDK也只提供了二階和三階的API呼叫。對於再高階的貝塞爾曲線,通常可以將曲線拆分成多個低階的貝塞爾曲線,也就是所謂的降階操作。下面將通過程式碼來模擬二階和三階的貝塞爾曲線是如何繪製和控制的。

貝塞爾曲線的一個比較好的動態演示如下所示:

http://myst729.github.io/bezier-curve/

這裡寫圖片描述

二階模擬

二階貝塞爾曲線在Android中的API為:quadTo()和rQuadTo(),這兩個API在原理上是可以互相轉換的——quadTo是基於絕對座標,而rQuadTo是基於相對座標,所以後面我都只以其中一個來進行講解。

先來看下最終的效果:

這裡寫圖片描述

從前面的介紹可以知道,二階貝塞爾曲線有兩個資料點和一個控制點,只需要在程式碼中繪製出這些輔助點和輔助線即可,同時,控制點可以通過onTouchEvent來進行傳遞。

package com.xys.animationart.views;import
android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;/** * 二階貝塞爾曲線 * <p/> * Created by xuyisheng on 16/7/11. */public class SecondOrderBezier extends View {    private Paint mPaintBezier;    private Paint mPaintAuxiliary;    private Paint mPaintAuxiliaryText;    private float mAuxiliaryX;    private float mAuxiliaryY;    private float mStartPointX;    private float mStartPointY;    private float mEndPointX;    private float mEndPointY;    private Path mPath = new Path();    public SecondOrderBezier(Context context) {        super(context);    }    public SecondOrderBezier(Context context, AttributeSet attrs) {        super(context, attrs);        mPaintBezier = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaintBezier.setStyle(Paint.Style.STROKE);        mPaintBezier.setStrokeWidth(8);        mPaintAuxiliary = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaintAuxiliary.setStyle(Paint.Style.STROKE);        mPaintAuxiliary.setStrokeWidth(2);        mPaintAuxiliaryText = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaintAuxiliaryText.setStyle(Paint.Style.STROKE);        mPaintAuxiliaryText.setTextSize(20);    }    public SecondOrderBezier(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mStartPointX = w / 4;        mStartPointY = h / 2 - 200;        mEndPointX = w / 4 * 3;        mEndPointY = h / 2 - 200;    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPath.reset();        mPath.moveTo(mStartPointX, mStartPointY);        // 輔助點        canvas.drawPoint(mAuxiliaryX, mAuxiliaryY, mPaintAuxiliary);        canvas.drawText("控制點", mAuxiliaryX, mAuxiliaryY, mPaintAuxiliaryText);        canvas.drawText("起始點", mStartPointX, mStartPointY, mPaintAuxiliaryText);        canvas.drawText("終止點", mEndPointX, mEndPointY, mPaintAuxiliaryText);        // 輔助線        canvas.drawLine(mStartPointX, mStartPointY, mAuxiliaryX, mAuxiliaryY, mPaintAuxiliary);        canvas.drawLine(mEndPointX, mEndPointY, mAuxiliaryX, mAuxiliaryY, mPaintAuxiliary);        // 二階貝塞爾曲線        mPath.quadTo(mAuxiliaryX, mAuxiliaryY, mEndPointX, mEndPointY);        canvas.drawPath(mPath, mPaintBezier);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_MOVE:                mAuxiliaryX = event.getX();                mAuxiliaryY = event.getY();                invalidate();        }        return true;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

三階模擬

三階貝塞爾曲線在Android中的API為:cubicTo()和rCubicTo(),這兩個API在原理上是可以互相轉換的——quadTo是基於絕對座標,而rCubicTo是基於相對座標,所以後面我都只以其中一個來進行講解。

有了二階的基礎,再來模擬三階就非常簡單了,無非是增加了一個控制點而已,先看下效果圖:

這裡寫圖片描述

程式碼只需要在二階的基礎上新增一些輔助點即可,下面只給出一些關鍵程式碼,詳細程式碼請參考Github:

    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPath.reset();        mPath.moveTo(mStartPointX, mStartPointY);        // 輔助點        canvas.drawPoint(mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary);        canvas.drawText("控制點1", mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliaryText);        canvas.drawText("控制點2", mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliaryText);        canvas.drawText("起始點", mStartPointX, mStartPointY, mPaintAuxiliaryText);        canvas.drawText("終止點", mEndPointX, mEndPointY, mPaintAuxiliaryText);        // 輔助線        canvas.drawLine(mStartPointX, mStartPointY, mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary);        canvas.drawLine(mEndPointX, mEndPointY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary);        canvas.drawLine(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary);        // 三階貝塞爾曲線        mPath.cubicTo(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mEndPointX, mEndPointY);        canvas.drawPath(mPath, mPaintBezier);    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

模擬網頁

如下所示的網頁,模擬了三階貝塞爾曲線的繪製,可以通過拖動曲線來獲取兩個控制點的座標,而起始點分別是(0,0)和(1,1)。

http://cubic-bezier.com/

這裡寫圖片描述

通過這個網頁,也可以比較方便的獲取三階貝塞爾曲線的控制點座標。

貝塞爾曲線應用

圓滑繪圖

當在螢幕上繪製路徑時,例如手寫板,最基本的方法是通過Path.lineTo將各個觸點連線起來,而這種方式在很多時候會發現,兩個點的連線是非常生硬的,因為它畢竟是通過直線來連線的,如果通過二階貝塞爾曲線來將各個觸點連線,就會圓滑的多,不會出現太多的生硬連線。

先來看下程式碼,非常簡單的繪製路徑程式碼:

package com.xys.animationart.views;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;/** * 圓滑路徑 * <p/> * Created by xuyisheng on 16/7/19. */public class DrawPadBezier extends View {    private float mX;    private float mY;    private float offset = ViewConfiguration.get(getContext()).getScaledTouchSlop();    private Paint mPaint;    private Path mPath;    public DrawPadBezier(Context context) {        super(context);    }    public DrawPadBezier(Context context, AttributeSet attrs) {        super(context, attrs);        mPath = new Path();        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setStrokeWidth(5);        mPaint.setColor(Color.RED);    }    public DrawPadBezier(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mPath.reset();                float x = event.getX();                float y = event.getY();                mX = x;                mY = y;                mPath.moveTo(x, y);                break;            case MotionEvent.ACTION_MOVE:                float x1 = event.getX();                float y1 = event.getY();                float preX = mX;                float preY = mY;                float dx = Math.abs(x1 - preX);                float dy = Math.abs(y1 - preY);                if (dx >= offset || dy >= offset) {                    // 貝塞爾曲線的控制點為起點和終點的中點                    float cX = (x1 + preX) / 2;                    float cY = (y1 + preY) / 2;//                    mPath.quadTo(preX, preY, cX, cY);                    mPath.lineTo(x1, y1);                    mX = x1;                    mY = y1;                }        }        invalidate();        return true;    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.drawPath(mPath, mPaint);    }}
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

先來看下通過mPath.lineTo來實現的繪圖,效果如下所示:

這裡寫圖片描述

圖片中的拐點有明顯的鋸齒效果,即通過直線的連線,再來看下通過貝塞爾曲線來連線的效果,通常情況下,貝塞爾曲線的控制點取兩個連續點的中點:

mPath.quadTo(preX, preY, cX, cY);
   
  • 1

通過二階貝塞爾曲線的連線效果如圖所示:

這裡寫圖片描述

可以明顯的發現,曲線變得更加圓滑了。

曲線變形

通過控制貝塞爾曲線的控制點,就可以實現對一條路徑的修改。所以,利用貝塞爾曲線,可以實現很多的路徑動畫,例如:

這裡寫圖片描述

package com.xys.animationart;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.View;import android.view.animation.BounceInterpolator;/** * 曲線變形 * <p/> * Created by xuyisheng on 16/7/11. */public class PathMorphBezier extends View implements View.OnClickListener{    private Paint mPaintBezier;    private Paint mPaintAuxiliary;    private Paint mPaintAuxiliaryText;    private float mAuxiliaryOneX;    private float mAuxiliaryOneY;    private float mAuxiliaryTwoX;    private float mAuxiliaryTwoY;    private float mStartPointX;    private float mStartPointY;    private float mEndPointX;    private float mEndPointY;    private Path mPath = new Path();    private ValueAnimator mAnimator;    public PathMorphBezier(Context context) {        super(context);    }    public PathMorphBezier(Context context, AttributeSet attrs) {        super(context, attrs);        mPaintBezier = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaintBezier.setStyle(Paint.Style.STROKE);        mPaintBezier.setStrokeWidth(8);        mPaintAuxiliary = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaintAuxiliary.setStyle(Paint.Style.STROKE);        mPaintAuxiliary.setStrokeWidth(2);        mPaintAuxiliaryText = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaintAuxiliaryText.setStyle(Paint.Style.STROKE);        mPaintAuxiliaryText.setTextSize(20);        setOnClickListener(this);    }    public PathMorphBezier(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mStartPointX = w / 4;        mStartPointY = h / 2 - 200;        mEndPointX = w / 4 * 3;        mEndPointY = h / 2 - 200;        mAuxiliaryOneX = mStartPointX;        mAuxiliaryOneY = mStartPointY;        mAuxiliaryTwoX = mEndPointX;        mAuxiliaryTwoY = mEndPointY;        mAnimator = ValueAnimator.ofFloat(mStartPointY, (float) h);        mAnimator.setInterpolator(new BounceInterpolator());        mAnimator.setDuration(1000);        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator) {                mAuxiliaryOneY = (float) valueAnimator.getAnimatedValue();                mAuxiliaryTwoY = (float) valueAnimator.getAnimatedValue();                invalidate();            }        });    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPath.reset();        mPath.moveTo(mStartPointX, mStartPointY);        // 輔助點        canvas.drawPoint(mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary);        canvas.drawText("輔助點1", mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliaryText);        canvas.drawText("輔助點2", mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliaryText);        canvas.drawText("起始點", mStartPointX, mStartPointY, mPaintAuxiliaryText);        canvas.drawText("終止點", mEndPointX, mEndPointY, mPaintAuxiliaryText);        // 輔助線        canvas.drawLine(mStartPointX, mStartPointY, mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary);        canvas.drawLine(mEndPointX, mEndPointY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary);        canvas.drawLine(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary);        // 三階貝塞爾曲線        mPath.cubicTo(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mEndPointX, mEndPointY);        canvas.drawPath(mPath, mPaintBezier);    }    @Override    public void onClick(View view) {        mAnimator.start();    }}
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

這裡就是簡單的改變二階貝塞爾曲線的控制點來實現曲線的變形。

網上一些比較複雜的變形動畫效果,也是基於這種實現方式,其原理都是通過改變控制點的位置,從而達到對圖形的變換,例如圓形到心形的變化、圓形到五角星的變換,等等。

波浪效果

波浪的繪製是貝塞爾曲線一個非常簡單的應用,而讓波浪進行波動,其實並不需要對控制點進行改變,而是可以通過位移來實現,這裡我們是藉助貝塞爾曲線來實現波浪的繪製效果,效果如圖所示:

這裡寫圖片描述

package com.xys.animationart.views;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.View;import android.view.animation.LinearInterpolator;/** * 波浪圖形 * <p/> * Created by xuyisheng on 16/7/11. */public class WaveBezier extends View implements View.OnClickListener {    private Paint mPaint;    private Path mPath;    private int mWaveLength = 1000;    private int mOffset;    private int mScreenHeight;    private int mScreenWidth;    private int mWaveCount;    private int mCenterY;    public WaveBezier(Context context) {        super(context);    }    public WaveBezier(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    public WaveBezier(Context context, AttributeSet attrs) {        super(context, attrs);        mPath = new Path();        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setColor(Color.LTGRAY);        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);        setOnClickListener(this);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mScreenHeight = h;        mScreenWidth = w;        mWaveCount = (int) Math.round(mScreenWidth / mWaveLength + 1.5);        mCenterY = mScreenHeight / 2;    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPath.reset();        mPath.moveTo(-mWaveLength + mOffset, mCenterY);        for (int i = 0; i < mWaveCount; i++) {            // + (i * mWaveLength)            // + mOffset            mPath.quadTo((-mWaveLength * 3 / 4) + (i * mWaveLength) + mOffset, mCenterY + 60, (-mWaveLength / 2) + (i * mWaveLength) + mOffset, mCenterY);            mPath.quadTo((-mWaveLength / 4) + (i * mWaveLength) + mOffset, mCenterY - 60, i * mWaveLength + mOffset, mCenterY);        }        mPath.lineTo(mScreenWidth, mScreenHeight);        mPath.lineTo(0, mScreenHeight);        mPath.close();        canvas.drawPath(mPath, mPaint);    }    @Override    public void onClick(View view) {        ValueAnimator animator = ValueAnimator.ofInt(0, mWaveLength);        animator.setDuration(1000);        animator.setRepeatCount(ValueAnimator.INFINITE);        animator.setInterpolator(new LinearInterpolator());        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mOffset = (int) animation.getAnimatedValue();                postInvalidate();            }        });        animator.start();    }}
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

波浪動畫實際上並不複雜,但三角函式確實對一些開發者比較困難,開發者可以通過下面的這個網站來模擬三角函式影象的繪製:

https://www.desmos.com/calculator

這裡寫圖片描述

路徑動畫

貝塞爾曲線的另一個非常常用的功能,就是作為動畫的運動軌跡,讓動畫目標能夠沿曲線平滑的實現移動動畫,也就是讓物體沿著貝塞爾曲線運動,而不是機械的直線,本例實現效果如下所示:

這裡寫圖片描述

package com.xys.animationart.views;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PointF;import android.util.AttributeSet;import android.view.View;import android.view.animation.AccelerateDecelerateInterpolator;import com.xys.animationart.evaluator.BezierEvaluator;/** * 貝塞爾路徑動畫 * <p/> * Created by xuyisheng on 16/7/12. */public class PathBezier extends View implements View.OnClickListener {    private Paint mPathPaint;    private Paint mCirclePaint;    private int mStartPointX;    private int mStartPointY;    private int mEndPointX;    private int mEndPointY;    private int mMovePointX;    private int mMovePointY;    private int mControlPointX;    private int mControlPointY;    private Path mPath;    public PathBezier(Context context) {        super(context);    }    public PathBezier(Context context, AttributeSet attrs) {        super(context, attrs);        mPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPathPaint.setStyle(Paint.Style.STROKE);        mPathPaint.setStrokeWidth(5);        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mStartPointX = 100;        mStartPointY = 100;        mEndPointX = 600;        mEndPointY = 600;        mMovePointX = mStartPointX;        mMovePointY = mStartPointY;        mControlPointX = 500;        mControlPointY = 0;        mPath = new Path();        setOnClickListener(this);    }    public PathBezier(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPath.reset();        canvas.drawCircle(mStartPointX, mStartPointY, 30, mCirclePaint);        canvas.drawCircle(mEndPointX, mEndPointY, 30, mCirclePaint);        mPath.moveTo(mStartPointX, mStartPointY);        mPath.quadTo(mControlPointX, mControlPointY, mEndPointX, mEndPointY);        canvas.drawPath(mPath, mPathPaint);        canvas.drawCircle(mMovePointX, mMovePointY, 30, mCirclePaint);    }    @Override    public void onClick(View view) {        BezierEvaluator bezierEvaluator = new BezierEvaluator(new PointF(mControlPointX, mControlPointY));        ValueAnimator anim = ValueAnimator.ofObject(bezierEvaluator,                new PointF(mStartPointX, mStartPointY),                new PointF(mEndPointX, mEndPointY));        anim.setDuration(600);        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator) {                PointF point = (PointF) valueAnimator.getAnimatedValue();                mMovePointX = (int) point.x;                mMovePointY = (int) point.y;                invalidate();            }        });        anim.setInterpolator(new AccelerateDecelerateInterpolator());        anim.start();    }}
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

其中,用於改變運動點座標的關鍵evaluator如下所示:

package com.xys.animationart.evaluator;import android.animation.TypeEvaluator;import android.graphics.PointF;import com.xys.animationart.util.BezierUtil;public class BezierEvaluator implements TypeEvaluator<PointF> {    private PointF mControlPoint;    public BezierEvaluator(PointF controlPoint) {        this.mControlPoint = controlPoint;    }    @Override    public PointF evaluate(float t, PointF startValue, PointF endValue) {        return BezierUtil.CalculateBezierPointForQuadratic(t, startValue, mControlPoint, endValue);    }}
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

這裡的TypeEvaluator計算用到了計算貝塞爾曲線上點的計算演算法,這個會在後面繼續講解。

貝塞爾曲線進階

求貝塞爾曲線上任意一點的座標

求貝塞爾曲線上任意一點的座標,這一過程,就是利用了De Casteljau演算法。

http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html

這裡寫圖片描述

利用這一演算法,有開發者開發了一個演示多階貝塞爾曲線的效果的App,其原理就是通過繪製貝塞爾曲線上的點來進行繪製的,地址如下所示:

https://github.com/venshine/BezierMaker

下面這篇文章就詳細的講解了該演算法的應用,我的程式碼也從這裡提取而來:

http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/

計算

有了公式,只需要程式碼實現就OK了,我們先寫兩個公式:

package com.xys.animationart.util;import android.graphics.PointF;/** * 計算貝塞爾曲線上的點座標 * <p/> * Created by xuyisheng on 16/7/13. */public class BezierUtil {    /**     * B(t) = (1 - t)^2 * P0 + 2t * (1 - t) * P1 + t^2 * P2, t ∈ [0,1]     *     * @param t  曲線長度比例     * @param p0 起始點     * @param p1 控制點     * @param p2 終止點     * @return t對應的點     */    public static PointF CalculateBezierPointForQuadratic(float t, PointF p0, PointF p1, PointF p2) {        PointF point = new PointF();        float temp = 1 - t;        point.x = temp * temp * p0.x + 2 * t * temp * p1.x + t * t * p2.x;        point.y = temp * temp * p0.y + 2 * t * temp * p1.y + t * t * p2.y;        return point;    }    /**     * B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3, t ∈ [0,1]     *     * @param t  曲線長度比例     * @param p0 起始點     * @param p1 控制點1     * @param p2 控制點2     * @param p3 終止點     * @return t對應的點     */    public static PointF CalculateBezierPointForCubic(float t, PointF p0, PointF p1, PointF p2, PointF p3