Path即路徑,可以將其想象成一條或多條線段,它們可能是直的,也可能是彎曲的。一般來說只有一條,即連貫的,除非以下兩種情況:
1、調用
addXXX
方法,直接添加固定形狀的路徑。
2、調用moveTo
函數改變繪制起始位置。
1、Path的方向
Path.Direction
,用于將封閉形狀
(例如矩形,橢圓形)添加到Path時,計算其內部像素點是被填充
,還是被鏤空
:
Path.Direction.CW
表示順時針。
Path.Direction.CCW
表示逆時針。
注意:計算規則根據FillType不同而不同
。
2、Path填充模式
setFillType(FillType fillType)
FileType有以下取值:WINDING(默認值)
、EVEN_ODD
、INVERSE_WINDING
、INVERSE_EVEN_ODD
1、WINDING(默認值)
這里畫了6個圖形,1和2是關于y=300對稱,方向相反,其余的都是以(300,300)為中心,他們的邊界已用黑線在圖中標記。
WINDING判斷一個像素點是被填充
,還是被鏤空
的規則如下:
1、從該點引出一條
假想線
到外層區域,如圖中紅線。
2、計算從該點出發所穿過的邊界,如果遇到順時針,則+1,逆時針,則-1。
3、計算的結果,如果為0,則表示該點被鏤空,否則該點被填充顏色。
2、EVEN_ODD
在原先代碼中,設置FileType為EVEN_ODD
:
EVEN_ODD判斷一個像素點是被填充
,還是被鏤空
的規則如下:
1、從該點引出一條
假想線
到外層區域。
2、計算從該點出發所穿過的邊界數量。
3、如果穿過數量為奇數,表示被填充,偶數則被鏤空。
3、INVERSE_WINDING
在原先代碼中,設置FileType為INVERSE_WINDING
:
INVERSE_WINDING判斷一個像素點是被填充
,還是被鏤空
的規則如下:
1、從該點引出一條
假想線
到外層區域。
2、計算從該點出發所穿過的邊界,如果遇到順時針,則+1,逆時針,則-1。
3、計算的結果,如果為0,則表示該點被填充顏色,否則該點被鏤空。
4、INVERSE_EVEN_ODD
在原先代碼中,設置FileType為INVERSE_EVEN_ODD
:
INVERSE_EVEN_ODD判斷一個像素點是被填充
,還是被鏤空
的規則如下:
1、從該點引出一條
假想線
到外層區域。
2、計算從該點出發所穿過的邊界數量。
3、如果穿過數量為偶數,表示被填充,奇數則被鏤空。
3、重置路徑
rewind()
清除FillType及所有的直線、曲線、點的數據,但會保留數據結構,這樣可以實現快速重用,提高一定的性能。
reset()
新建一個路徑對象,它的所有數據空間都會被回收并重新分配,但不會清除FillType。
對比:rewind()函數不會清除內存,但會清除FillType;而reset()函數會清除內存,但不會清除FillType
4、PathMeasure
PathMeasure是Path的一個輔助類,雖然它跟繪制無關,但它有以下2個主要作用:
1、獲取Path中某個子線段。
2、獲取Path中某個位置的坐標和切線角度。
這意味著它配合屬性動畫,能夠實現各種Path特效。
PathMeasure需要關聯一個創建好的path,以及指定forceClose,表示Path是否需要閉合,它會影響path的測量結果。
1、獲取路徑的長度
float getLength()
2、分割路徑
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
startWithMoveTo
為true時,表示將dst
中的終點,設置為本次分割結果的起始點。
- 截取的結果跟Path的方向有關。
- 會將截取的Path片段添加到dst中,而不是替換dst中的內容。
3、獲取路徑上某點的坐標和切線
3.1、方式1:
boolean getPosTan(float distance, float pos[], float tan[])
pos[0]、pos[1]分別表示當前distance位置的x,y坐標
tan表示該點的切線與單位圓相交的坐標點,可通過以下公式求出角度。
double degress = Math.atan2(tan[0], tan[1]) * 180.0 / Math.PI;
3.2、方式2:
boolean getMatrix(float distance, Matrix matrix, int flags)
這個函數用于得到路徑上某一長度的位置以及該位置的正切值的矩陣。
flags
可指定以下參數,并且可通過|
來同時傳入:
POSITION_MATRIX_FLAG
:表示獲取位置信息
TANGENT_MATRIX_FLAG
:表示獲取切線信息
4、跳轉到下一條線段
boolean nextContour()
由于Path可以包含多條線段,但getLength()、getSegment()還是其他函數,都只針對第一條線段進行計算,而nextContour()就是用于跳轉到下一條曲線的函數。
5、貝塞爾曲線
二階貝塞爾曲線
void quadTo(float x1, float y1, float x2, float y2)
void rQuadTo(float dx1, float dy1, float dx2, float dy2)
三階貝塞爾曲線
void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
注意:函數前帶r的貝塞爾函數,表示坐標位置都是相對位置,參照于Path中前一個線段的終點。
下面解釋幾種貝塞爾曲線在真實場景下的實際應用。
5.1、平滑線條
利用貝塞爾曲線,可以改善書寫時的邊緣鋸齒。
public class DrawBezierView extends View {
private Path mPath;
private Paint mPaint;
private float mPreX;
private float mPreY;
public DrawBezierView(Context context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPath = new Path();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPreX = event.getX();
mPreY = event.getY();
mPath.moveTo(mPreX, mPreY);
break;
case MotionEvent.ACTION_MOVE:
float endX = (event.getX() + mPreX) / 2;
float endY = (event.getY() + mPreY) / 2;
mPath.quadTo(mPreX, mPreY, endX, endY);
mPreX = event.getX();
mPreY = event.getY();
invalidate();
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath, mPaint);
}
}