Android 貝塞爾曲線

鋸齒流沙發表於2017-12-26

什麼是貝塞爾曲線(Bézier曲線)?

貝塞爾曲線,又稱貝茲曲線或貝濟埃曲線,是應用於二維圖形應用程式的數學曲線。一般的向量圖形軟體通過它來精確畫出曲線,貝茲曲線由線段與節點組成,節點是可拖動的支點,線段像可伸縮的皮筋,我們在繪圖工具上看到的鋼筆工具就是來做這種向量曲線的。貝塞爾曲線是計算機圖形學中相當重要的引數曲線,在一些比較成熟的點陣圖軟體中也有貝塞爾曲線工具,如PhotoShop等。貝賽爾曲線是電腦圖形學中相當重要的引數曲線。可以這麼說基本上任何的曲線和曲面都可以用貝塞爾公式來解決。

貝塞爾曲線分為一階貝塞爾曲線、二階貝塞爾曲線、三階貝塞爾曲線.......n階貝塞爾曲線,而一階貝塞爾曲線就是一條直線,在Android中可以通過Path的quadTo方法和cubicTo方法分別繪製出二階貝塞爾曲線和三階貝塞爾曲線。

一階貝塞爾曲線

一階貝塞爾曲線沒有控制點,僅有兩個資料點,也就是一條直線

一階貝塞爾曲線.gif

二階貝塞爾曲線

二階貝塞爾曲線僅有一個控制點,其表示公式:

二階貝塞爾曲線

二階貝塞爾曲線的路徑由給定點P0、P1、P2的函式B(t)追蹤,P0和P2是曲線的始點和終點,P1是控制點。

我們看下t的變化:

貝塞爾曲線

貝塞爾曲線.gif

t的變化直接影響到曲線的弧度。繪製二階貝塞爾曲線在Android中使用Path提供的quadTo方法繪製。

三階貝塞爾曲線

P0、P1、P2、P3四個點在平面或在三維空間中定義了三次方貝茲曲線。曲線起始於P0走向P1,並從P2的方向來到P3。一般不會經過P1或P2;這兩個點只是在那裡提供方向資訊。P0和P1之間的間距,決定了曲線在轉而趨進P3之前,走向P2方向的“長度有多長”。

貝塞爾曲線.png

貝塞爾曲線.jpeg

貝塞爾曲線.gif

Android中繪製三階的貝塞爾曲線,對應Path的cubicTo方法。

N階貝塞爾曲線

N階貝塞爾曲線更加複雜,其表示的公式如下:

N階貝塞爾曲線.png

如上公式可如下遞迴表達: 用表示由點P0、P1、…、Pn所決定的貝茲曲線。

動態演示過程:

N階貝塞爾曲線.gif

看了以上的公式,是不是已經頭暈腦脹了,沒關係,能夠看明白貝塞爾曲線的演示過程就行了。如果還不是很明白,推薦可以The Bézier Game來玩一下,也可以參考《貝塞爾曲線掃盲》這篇文章,裡面的圖形講解更加形象。 那麼貝塞爾曲線有什麼用呢?貝塞爾曲線在平時的APP中隨處可見,如:qq的提示紅點拖拽時的粘性過程、直播中的愛心的浮動路徑、手機充電時的圖形等等。

關於貝塞爾去曲線的應用,網上搜尋以下一大堆demo,本文看一個簡單的應用:模仿水波上漲的情況。

public class WaveView extends View {

	private Paint paint;
	private Path path;
	private int waveLength = 800;
	private int dx;
	private int dy;

	public WaveView(Context context) {
		this(context, null);
		init();
	}
	public WaveView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	private void init() {
		paint = new Paint();
		paint.setColor(0xff84C1ff);
		paint.setStyle(Style.FILL_AND_STROKE);

		path = new Path();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		path.reset();
		int originY = 1000;
		if(dy<originY + 150){
			dy += 5;
		}
		int halfWaveLength = waveLength/2;
		path.moveTo(-waveLength+dx, originY-dy);
		//螢幕的寬度裡面放多少個波長
		for (int i = -waveLength; i < getWidth() + waveLength; i += waveLength) {
			//相對繪製二階貝塞爾曲線(相對於自己的起始點--也即是上一個曲線的終點  的距離dx1)
			path.rQuadTo(halfWaveLength/2, -150, halfWaveLength, 0);
			path.rQuadTo(halfWaveLength/2, 150, halfWaveLength, 0);
		}
		//顏色填充
		//畫一個封閉的空間
		path.lineTo(getWidth(), getHeight());
		path.lineTo(0, getHeight());
		path.close();

		canvas.drawPath(path, paint);
	}

	public void startAnimation(){
		ValueAnimator animator = ValueAnimator.ofInt(0,waveLength);
		animator.setDuration(3000);
		animator.setInterpolator(new LinearInterpolator());
		//無限迴圈
		animator.setRepeatCount(ValueAnimator.INFINITE);
		animator.addUpdateListener(new AnimatorUpdateListener() {

			@Override
			public void onAnimationUpdate(ValueAnimator animation) {
				dx = (int) animation.getAnimatedValue();
				postInvalidate();
			}
		});
		animator.start();
	}

}
複製程式碼

貝塞爾曲線

使用rQuadTo繪製二階的貝塞爾曲線,呼叫兩次,一個繪製波峰,一個繪製波谷。然後使用動畫來改變dx和dy來模擬上漲和水波運動的情況。

上面用到的動畫來改變dx,也可以使用以下寫法:

    private class MoveAnimation extends Animation {

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            mInterpolatedTime = interpolatedTime;
            Log.i("lwj","mInterpolatedTime:"+mInterpolatedTime);
            invalidate();
        }
    }

    public void startAnimation() {
        mPath.reset();
        mInterpolatedTime = 0;
        MoveAnimation move = new MoveAnimation();
        move.setDuration(1000);
        move.setInterpolator(new AccelerateDecelerateInterpolator());
//        move.setRepeatCount(Animation.INFINITE);
//        move.setRepeatMode(Animation.REVERSE);
        startAnimation(move);
    }
複製程式碼

這裡拿到的數值mInterpolatedTime範圍式[0,1]。

使用:

public class GallaryHorizonalScrollViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        WaveView view = new WaveView(this);
        setContentView(view);
        view.startAnimation();

    }
}
複製程式碼

關於貝塞爾曲線的介紹和使用,大家可以參考: 《Android 自定義View高階特效,神奇的貝塞爾曲線》 《Android-貝塞爾曲線》 《三次貝塞爾曲線練習之彈性的圓》

相關文章