一、概述
在實際的開發當中,我們經常會涉及到繪製路徑,這裡我們總結一下Path
的常用API
。
二、基本用法
對於一個Path
來說,它其中有很多的”子路徑“,對於每個”子路徑“,它又會有兩個變數,"源點"和"當前點",也就是原始碼當中所描述的:
The Path class encapsulates compound (multiple contour) geometric paths
複製程式碼
2.1 簡單連線 - xxxTo
下面是Path
當中和連線相關的方法:
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/261fb8ff759ab9d010d9cdc8ce5774095a74407714f41e4656313843258fc08c.png)
public void moveTo(float x, float y)
public void rMoveTo(float dx, float dy)
複製程式碼
每次呼叫完moveTo/rMoveTo
方法,都會生成一條新的子路徑,這兩者的區別在於:
1.
新建一條路徑,(x, y)
作為這個新路徑的“源點"的值,在圖上不會產生新的連線。
2.
相對於當前路徑的"當前點"的值,移動座標(dx, dy)
,作為新路徑的"源點",在圖上不會產生新的連線。
public void lineTo(float x, float y)
public void rLineTo(float dx, float dy)
複製程式碼
1.
從當前點位置,直線連線到絕對座標(x, y)
,如果沒有呼叫過moveTo/rMoveTo
,那麼會先呼叫moveTo(0, 0)
來生成一條從源點開始的子路徑。
2.
相對於當前點座標,移動(dx, dy)
作為終點座標,並連線起點和終點。
public void quadTo(float x1, float y1, float x2, float y2)
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)
複製程式碼
1.
從當前點位置開始,以(x1, y1)
為控制點,按一階貝塞爾曲線計算方法連線到(x2, y2)
,如果之前沒有呼叫過moveTo/rMoveTo
,也會先呼叫一次moveTo(0, 0)
方法。
2.
類似於上面,只不過終點座標是相對於當前點座標移動了(dx2, dy2)
後的值。
public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
複製程式碼
和一階貝塞爾曲線類似,不過是多了一個控制點(x2, y2)
。
public void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
public void arcTo(RectF oval, float startAngle, float sweepAngle)
public void arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)
複製程式碼
上面這三個函式,最終都是呼叫了同一個方法:
native_arcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo)
複製程式碼
它的原理就是:首先根據RectF
或者4
個點的座標來確定一個矩形區域,之後得到這個矩形的內切橢圓,然後再根據startAngle
和sweepAngle
來確定這個內切源的一段弧,這段弧就是新繪製的路徑。但是這段弧的起點很有可能和Path
的當前點不是重合的,這時候就根據forceMoveTo
來決定是否產生一條新的子路徑,從最終的結果看,就是是否需要繪製一條從Path
當前點到這段弧起點的連線,如果為forceMoveTo=false
,那麼就繪製這麼一條直線,反之,則不繪製,forceMoveTo
的預設值為false
。
forceMoveTo=false
private void drawArcTo(Canvas canvas) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
Path path = new Path();
path.moveTo(0, 300);
path.lineTo(300, 300);
path.arcTo(0, 0, 500, 500, 0, -90, false);
path.rLineTo(0, 250);
canvas.drawPath(path, mPaint);
}
複製程式碼
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/fb611d9c653f39c40d8f90fd6f1f36e801af700a1c0f73b066dfc9f7bcf4f00d.png)
forceMoveTo=true
private void drawArcTo(Canvas canvas) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
Path path = new Path();
path.moveTo(0, 300);
path.lineTo(300, 300);
path.arcTo(0, 0, 500, 500, 0, -90, true);
path.rLineTo(0, 250);
canvas.drawPath(path, mPaint);
}
複製程式碼
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/e73ab6133d457c626b0ddfa22dc0d510841cc6771e7360d11a3a30e89607358a.png)
forceMoveTo=true + path.close()
private void drawArcTo(Canvas canvas) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
Path path = new Path();
path.moveTo(0, 300);
path.lineTo(300, 300);
path.arcTo(0, 0, 500, 500, 0, -90, true);
path.rLineTo(0, 250);
path.close();
canvas.drawPath(path, mPaint);
}
複製程式碼
對於這種情況,由於採用了true
標誌位,因此生成了一條新的”子路徑“,所以在呼叫close
方法之後,連線的是當前子路徑的源點和當前點。
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/592beae18ab2812d10218b5495e0d00c6882a514486b498408ab32967282fd88.png)
public void close()
複製程式碼
如果對於當前”子路徑“來說,它的"當前點"和"源點"不重合,那麼會繪製一條從當前點到源點的直線。
2.2 直接增加新的路徑
除了採用連線的方法來確定一條路徑,Path
還提供了addXXX
來直接地增加一整段的路徑,下面是相關的方法:
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/484e43bda4dee2cb456ea78b434e0ccd0067eab4f015019673943f059b42caeb.png)
addXXX
方法之後,會增加一條子路徑到Path
當中,並把該子路徑作為Path
的當前子路徑。
private void drawAddArc(Canvas canvas) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
Path path = new Path();
path.moveTo(0, 250);
path.lineTo(500, 250);
path.addArc(0, 0, 500, 500, 0, -90);
path.close();
canvas.drawPath(path, mPaint);
}
複製程式碼
下面是執行的結果:
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/8640804ed7e864986dd6d43483cb8038d31d8a6650c79dc04110e6efb37e7461.png)
2.3 填充型別FillType
關於填充型別的方法有如下這些:
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/eef2516ffc8444e2424570694de99709564a8a580ee2097f67a6ae14f628fb90.png)
FillType
的定義有哪些:
public enum FillType {
// these must match the values in SkPath.h
/**
* Specifies that "inside" is computed by a non-zero sum of signed
* edge crossings.
*/
WINDING (0),
/**
* Specifies that "inside" is computed by an odd number of edge
* crossings.
*/
EVEN_ODD (1),
/**
* Same as {@link #WINDING}, but draws outside of the path, rather than inside.
*/
INVERSE_WINDING (2),
/**
* Same as {@link #EVEN_ODD}, but draws outside of the path, rather than inside.
*/
INVERSE_EVEN_ODD(3);
FillType(int ni) {
nativeInt = ni;
}
final int nativeInt;
}
複製程式碼
關於這個FillType
的理解,下面這篇文章的作者說的很好:
我這裡只是稍微地總結一下:
2.3.1 FillType
的意義
我們都知道Paint
有三種模式:FILL/FILL_AND_STROKE/STROKE
,對於STROKE
來說,是隻繪製輪廓,而兩種模式都涉及到”填充“,那麼”填充“就涉及到怎麼定義一個Path
所組成的圖形的內部,FillType
就是用來確定這個所謂的”內部“的定義的,需要注意,只討論封閉圖形的情況。
2.3.2 FillType
的型別的含義
EVEN_ODD
表示奇偶規則:奇數表示在圖形內,偶數表示在圖形外,並繪製內部。 從任意位置p
作一條射線, 若與該射線相交的圖形邊的數目為奇數,則p
是圖形內部點,否則是外部點。INVERSE_EVEN_ODD
:和EVEN_ODD
對應,繪製外部。WINDING
表示非零環繞數規則:若環繞數為0
表示在圖形外,非零表示在圖形內,並繪製內部。 首先使圖形的邊變為向量。將環繞數初始化為零。再從任意位置p
作一條射線。當從p
點沿射線方向移動時,對在每個方向上穿過射線的邊計數,每當圖形的邊從右到左穿過射線時,環繞數加1
,從左到右時,環繞數減1
。處理完圖形的所有相關邊之後,若環繞數為非零,則p
為內部點,否則,p
是外部點。INVERSE_WINDING
:和WINDING
對應,繪製外部。
2.3.3 示例
private void drawFillType(Canvas canvas) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
Path fillTypePath = new Path();
//兩個圓圈都為順時針的情況.
fillTypePath.addCircle(250, 250, 250, Path.Direction.CW);
fillTypePath.addCircle(500, 500, 250, Path.Direction.CW);
//填充型別採用奇偶原則.
fillTypePath.setFillType(Path.FillType.EVEN_ODD);
canvas.drawPath(fillTypePath, mPaint);
}
複製程式碼
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/a5cce17854504a8a216e1712c976bfb562e5f8b3b50f4100222942806ae317ae.png)
private void drawFillType(Canvas canvas) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
Path fillTypePath = new Path();
//兩個圓圈都為順時針的情況.
fillTypePath.addCircle(250, 250, 250, Path.Direction.CW);
fillTypePath.addCircle(500, 500, 250, Path.Direction.CW);
//填充型別採用非零環繞數規則.
fillTypePath.setFillType(Path.FillType.WINDING);
canvas.drawPath(fillTypePath, mPaint);
}
複製程式碼
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/be2a6088a5d094d3ccbd7f9634996411376f67b24c5db901da21166d3705fa84.png)
private void drawFillType(Canvas canvas) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
Path fillTypePath = new Path();
//第一個圓圈為順時針,第二個圓圈為逆時針的情況.
fillTypePath.addCircle(250, 250, 250, Path.Direction.CW);
fillTypePath.addCircle(500, 500, 250, Path.Direction.CCW);
//填充型別採用奇偶原則.
fillTypePath.setFillType(Path.FillType.EVEN_ODD);
canvas.drawPath(fillTypePath, mPaint);
}
複製程式碼
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/a5cce17854504a8a216e1712c976bfb562e5f8b3b50f4100222942806ae317ae.png)
private void drawFillType(Canvas canvas) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
Path fillTypePath = new Path();
//第一個圓圈為順時針,第二個圓圈為逆時針的情況.
fillTypePath.addCircle(250, 250, 250, Path.Direction.CW);
fillTypePath.addCircle(500, 500, 250, Path.Direction.CCW);
//填充型別採用非零環繞數規則.
fillTypePath.setFillType(Path.FillType.WINDING);
canvas.drawPath(fillTypePath, mPaint);
}
複製程式碼
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/a5cce17854504a8a216e1712c976bfb562e5f8b3b50f4100222942806ae317ae.png)
2
和4
,由於Path.FillType.WINDING
會涉及到路徑的方向,因此路徑方向不同會影響它的結果,但是對比1
和3
,由於EVEN_ODD
的判斷只涉及到邊,不涉及到路徑的方向,因此不會影響它的結果。
2.4 Path
的計算
關於Path
的計算,有下面這兩個方法:
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/4c7d1e74f7802b39643f8a02b62b2eea3b39276ccbbfc0bb22d038295648adad.png)
Path
和引數中的Path
進行計算,計算結果放入當前Path
當中;第二個表示對引數內的兩個Path
進行計算,計算的結果放入到當前Path
中,計算的型別有以下幾種:
public enum Op {
/**
* Subtract the second path from the first path.
*/
DIFFERENCE,
/**
* Intersect the two paths.
*/
INTERSECT,
/**
* Union (inclusive-or) the two paths.
*/
UNION,
/**
* Exclusive-or the two paths.
*/
XOR,
/**
* Subtract the first path from the second path.
*/
REVERSE_DIFFERENCE
}
複製程式碼
DIFFERENCE
:從Path1
中減去Path2
。INTERSECT
:取Path1
和Path2
的交集。UNION
:取Path1
和Path2
的並集。XOR
:從Path1
和Path2
的並集中,減去它們的交集。REVERSE_DIFFERENCE
:從Path2
中減去Path1
。
我們以XOR
為例子:
private void drawOp(Canvas canvas) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
Path path1 = new Path();
//Path1為順時針.
path1.addCircle(250, 250, 250, Path.Direction.CW);
Path path2 = new Path();
//Path2為逆時針
path2.addCircle(400, 250, 250, Path.Direction.CCW);
path1.op(path2, Path.Op.XOR);
canvas.drawPath(path1, mPaint);
}
複製程式碼
最後的結果為:
![Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法](https://i.iter01.com/images/66346f1b891ecce0b0114b285c06eea48ce12fdd36db156a2dcbdfe42d913d41.png)
2.5 重置方法
Path
提供了兩種重置方法:
/**
* Clear any lines and curves from the path, making it empty.
* This does NOT change the fill-type setting.
*/
public void reset()
/**
* Rewinds the path: clears any lines and curves from the path but
* keeps the internal data structure for faster reuse.
*/
public void rewind()
複製程式碼
reset()
:清除路徑及其資訊,但保留FillType
。rewind()
:清除路徑,但保留資訊。