Android 高階UI10 Path及其貝塞爾曲線
Path工具類:用來記錄線條的軌跡路徑
canvas.draw(path,paint);
貝塞爾曲線
現實生活當中,任何的曲線和曲面都可以用貝塞爾公式來解決。
比如:
1.設計貝塞爾曲線或者貝塞爾曲線效果圖
2.怎麼去得到貝塞爾曲線的幾個要素(比如二階貝塞爾:p0初始位置,p1拐點,p2結束點)
p1拐點如何確定,可以通過工具來確定,比如Photoshop點鋼筆工具等
原理和簡單推導(以三階為例):
設 P 0 、 P 0 2 、 P 2 是一條拋物線上順序三個不同的點。過 P 0 和 P 2 點的兩切線交於 P 1 點,在 P 0 2 點的切線交 P 0 P 1 和 P 2 P 1 於 P 0 1 和 P 1 1 ,則如下比例成立:

這是所謂拋物線的三切線定理。

當 P 0 , P 2 固定,引入引數 t ,令上述比值為 t :(1- t ),即有:

t 從0變到1,第一、二式就分別表示控制二邊形的第一、二條邊,它們是兩條一次Bezier曲線。將一、二式代入第三式得:

當 t 從0變到1時,它表示了 由三頂點P 0 、P 1 、P 2 三點定義的一條二次Bezier曲線。
並且表明:
這 二次Bezier曲線 P 0 2 可以定義為分別由前兩個頂點( P 0 , P 1 )和後兩個頂點( P 1 , P 2 )決定的一次Bezier曲線的線性組合**。
依次類推,
由四個控制點定義的三次Bezier曲線 P 0 3 可被定義為分別由( P 0 , P 1 , P 2 )和( P 1 , P 2 , P 3 )確定的二條二次Bezier曲線的線性組合,由( n+ 1)個控制點 P i ( i =0,1,..., n )定義的 n 次Bezier曲線 P 0 n 可被定義為分別由前、後 n 個控制點定義的兩條( n -1)次Bezier曲線 P 0 n- 1 與 P 1 n- 1 的線性組合:

image
由此得到Bezier曲線的遞推計算公式

image
這就是這就是de Casteljau演算法,可以簡單闡述三階貝塞爾曲線原理。
Bézier curve(貝塞爾曲線)是應用於二維圖形應用程式的 數學曲線 。 曲線定義:起始點、終止點(也稱錨點)、控制點。通過調整控制點,貝塞爾曲線的形狀會發生變化。 1962年,法國數學家Pierre Bézier第一個研究了這種 向量 繪製曲線的方法,並給出了詳細的計算公式,因此按照這樣的公式繪製出來的曲線就用他的姓氏來命名,稱為貝塞爾曲線。
以下公式中:B(t)為t時間下 點的座標;
P 0 為起點,P n 為終點,P i 為控制點
一階貝塞爾曲線(線段):

image.png

image
意義:由 P0 至 P1 的連續點, 描述的一條線段
二階貝塞爾曲線(拋物線):


image
原理:由 P0 至 P1 的連續點 Q0,描述一條線段。
由 P1 至 P2 的連續點 Q1,描述一條線段。
由 Q0 至 Q1 的連續點 B(t),描述一條二次貝塞爾曲線。
經驗:P1-P0為曲線在P0處的切線。
三階貝塞爾曲線:


image
通用公式:

image.png
高階貝塞爾曲線:
4階曲線:

image
5階曲線:

image
二階貝塞爾曲線
public class WaveView extends View { private Paint paint; private Path path; public WaveView(Context context) { super(context); } public WaveView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init() { path = new Path(); paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Style.STROKE); paint.setStrokeWidth(10); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //第一個點--起始點 path.moveTo(100, 400); //二階貝塞爾曲線1 path.quadTo(250, 200, 400, 400); //二階貝塞爾曲線2(後面的曲線的起始點預設是接著上一個曲線的結束點) path.quadTo(550, 600, 700, 400); //關閉路徑(將起點和終點閉合) path.close(); canvas.drawPath(path, paint); } }

三階貝塞爾曲線
path.moveTo(100, 700); path.cubicTo(50, 500, 550, 500, 700, 700); canvas.drawPath(path, paint);

波浪例項
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) { super(context); } public WaveView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init() { path = new Path(); paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Style.FILL_AND_STROKE); paint.setStrokeWidth(10); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); path.reset(); int originY = 500; //if(dy<originY + 150){ //dy += 30; //} int halfWaveLength = waveLength / 2; path.moveTo(-waveLength + dx, originY - dy); //螢幕寬度裡面畫多少哥波長 for (int i = -waveLength; i < getWidth() + waveLength; i += waveLength) { //二階貝塞爾曲線1 /** * 相對於起始點的增量 */ 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(1000); 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(); } }
呼叫
WaveView waveView = findViewById(R.id.waveView); waveView.startAnimation();
