前一篇文章講了Path繪製直線以及各種基本圖形,這篇文主要說Path繪製曲線,就是曲線中非常有名的貝賽爾曲線。
貝賽爾曲線是由法國數學家Pierre Bézier所發明,由此爲計算機矢量圖形學奠定了基礎。它的主要意義在於無論是直線或曲線都能在數學上予以描述。
貝塞爾曲線作用十分廣泛:
- QQ小紅點拖拽效果
- 一些炫酷的下拉刷新控件
- 閱讀軟件的翻書效果
- 一些平滑的折線圖的製作
- 很多炫酷的動畫效果
理解貝塞爾曲線
塞爾曲線由起始點、數據點(也稱錨點)、控制點。通過調整控制點,貝塞爾曲線的形狀會發生變化。
數據點:確定曲線的起始和結束位置
控制點:確定曲線的彎曲程度
- 一階曲線原理:
一階曲線是沒有控制點的,僅有兩個數據點(A 和 B),最終效果一個線段。
- 二階曲線原理:
二階曲線由兩個數據點(A 和 C),一個控制點(B)來描述曲線狀態,大致如下:
上圖中紅色曲線部分就是傳說中的二階貝塞爾曲線,那麼這條紅色曲線是如何生成的呢?接下來我們就以其中的一個狀態分析一下:
連接AB BC,並在AB上取點D,BC上取點E,使其滿足條件:
連接DE,取點F,使得:
這樣獲取到的點F就是貝塞爾曲線上的一個點,動態過程如下:
- 三階曲線原理:
三階曲線由兩個數據點(A 和 D),兩個控制點(B 和 C)來描述曲線狀態,如下:
三階曲線計算過程與二階類似,具體可以見下圖動態效果:
貝塞爾曲線常用操作速查表
貝塞爾曲線 | 對應的方法 | 演示動畫 |
---|---|---|
一階曲線(線性曲線) | lineTo | |
二階曲線 | quadTo | |
三階曲線 | cubicTo | |
四階曲線 | 無 |
瞭解貝塞爾曲線相關函數使用方法
- 一階曲線:參照上篇文章Path的操作
- 二階曲線:二階曲線是由兩個數據點,一個控制點構成,兩個數據點是控制貝塞爾曲線開始和結束的位置,比較容易理解,而控制點則是控制貝塞爾的彎曲狀態,相對來說比較難以理解,所以本示例重點在於理解貝塞爾曲線彎曲狀態與控制點的關係。直接上代碼:
public class BezierTwo extends View {
private Paint mPaint;
private int centerX, centerY;
private PointF start, end, control; //起點,結束點,控制點
public BezierTwo(Context context) {
super(context);
init();
}
public BezierTwo(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BezierTwo(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(8);
//初始化起點,結束點,控制點
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;
//重新設置起點,結束點和控制點的位置
start.x = centerX - 200;
start.y = centerY;
end.x = centerY + 200;
end.y = centerY;
control.x = centerX;
control.y = centerY - 100;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//根據觸摸點更新控制點,並重繪
control.x = event.getX();
control.y = event.getY();
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪製數據點和控制點
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(end.x,end.y,control.x,control.y,mPaint);
//繪製貝賽爾曲線
mPaint.setStrokeWidth(8);
mPaint.setColor(Color.RED);
Path path=new Path();
path.moveTo(start.x,start.y);
path.quadTo(control.x,control.y,end.x,end.y);
canvas.drawPath(path,mPaint);
}
}
效果圖如下:
爲了更直觀,上圖還繪製了控制點和輔助線,從效果圖可以看出,貝賽爾曲線是有類似於橡皮筋的效果,所以經常用於繪製一些具有彈性的效果中。
- 三階曲線:三階曲線由兩個數據點和兩個控制點來控制曲線狀態。
public class BezierThree extends View {
private Paint mPaint;
private int centerX, centerY;
private PointF start, end, control1, control2;
private boolean mode = true;
public BezierThree(Context context) {
super(context);
init();
}
public BezierThree(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BezierThree(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.GRAY);
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);
}
public void setMode(boolean mode) {
this.mode = mode;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w / 2;
centerY = h / 2;
//初始化數據點和控制點
start.x = centerX - 200;
start.y = centerY;
end.x = centerX + 200;
end.y = centerY;
control1.x = centerX;
control1.y = centerY - 100;
control2.x = centerX;
control2.y = centerY - 100;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//根據觸摸位置更新控制點並重繪
if (mode) {
control1.x = event.getX();
control1.y = event.getY();
}else {
control2.x=event.getX();
control2.y=event.getY();
}
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪製數據點和控制點
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);
Path path=new Path();
path.moveTo(start.x,start.y);
path.cubicTo(control1.x,control1.y,control2.x,control2.y,end.x,end.y);
canvas.drawPath(path,mPaint);
}
效果如圖: