一、概述
在實際的開發當中,我們經常會涉及到繪制路徑,這里我們總結一下Path
的常用API
。
二、基本用法
對于一個Path
來說,它其中有很多的”子路徑“,對于每個”子路徑“,它又會有兩個變量,"源點"和"當前點",也就是源碼當中所描述的:
The Path class encapsulates compound (multiple contour) geometric paths
2.1 簡單連線 - xxxTo
下面是Path
當中和連線相關的方法:
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);
}
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);
}
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
方法之后,連接的是當前子路徑的源點和當前點。
public void close()
如果對于當前”子路徑“來說,它的"當前點"和"源點"不重合,那么會繪制一條從當前點到源點的直線。
2.2 直接增加新的路徑
除了采用連線的方法來確定一條路徑,Path
還提供了addXXX
來直接地增加一整段的路徑,下面是相關的方法:
效果其實從函數名上就可以很清楚地看出來,當調用完
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);
}
下面是運行的結果:
2.3 填充類型FillType
關于填充類型的方法有如下這些:
我們先來看一下
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);
}
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);
}
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);
}
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);
}
可以看到,對于
2
和4
,由于Path.FillType.WINDING
會涉及到路徑的方向,因此路徑方向不同會影響它的結果,但是對比1
和3
,由于EVEN_ODD
的判斷只涉及到邊,不涉及到路徑的方向,因此不會影響它的結果。
2.4 Path
的計算
關于Path
的計算,有下面這兩個方法:
其中,第一個表示對當前
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);
}
最后的結果為:
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()
:清除路徑,但保留信息。