(1)定義
Path顧名思義就是路徑的意思杆查,也可以說是軌跡的意思荚板,Path可以幫助view完成一些復(fù)雜的動畫效果。
(2)基本方法
作用 | 相關(guān)方法 | 備注 |
---|---|---|
移動起點 | moveTo | 移動下一次操作的起點位置 |
設(shè)置終點 | setLastPoint | 重置當(dāng)前path中最后一個點位置车猬,如果在繪制之前調(diào)用僚祷,效果和moveTo相同 |
連接直線 | lineTo | 添加上一個點到當(dāng)前點之間的直線到Path |
閉合路徑 | close | 連接第一個點連接到最后一個點,形成一個閉合區(qū)域 |
添加內(nèi)容 | addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo | 添加(矩形吼蚁, 圓角矩形凭需, 橢圓, 圓肝匆, 路徑粒蜈, 圓弧) 到當(dāng)前Path (注意addArc和arcTo的區(qū)別) |
是否為空 | isEmpty | 判斷Path是否為空 |
是否為矩形 | isRect | 判斷path是否是一個矩形 |
替換路徑 | set | 用新的路徑替換到當(dāng)前路徑所有內(nèi)容 |
偏移路徑 | offset | 對當(dāng)前路徑之前的操作進行偏移(不會影響之后的操作) |
貝塞爾曲線 | quadTo, cubicTo | 分別為二次和三次貝塞爾曲線的方法 |
rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | 不帶r的方法是基于原點的坐標(biāo)系(偏移量), rXxx方法是基于當(dāng)前點坐標(biāo)系(偏移量) |
填充模式 | setFillType, getFillType, isInverseFillType, toggleInverseFillType | 設(shè)置,獲取,判斷和切換填充模式 |
提示方法 | incReserve | 提示Path還有多少個點等待加入(這個方法貌似會讓Path優(yōu)化存儲結(jié)構(gòu)) |
布爾操作(API19) | op | 對兩個Path進行布爾運算(即取交集旗国、并集等操作) |
計算邊界 | computeBounds | 計算Path的邊界 |
重置路徑 | reset, rewind | 清除Path中的內(nèi)容枯怖,reset不保留內(nèi)部數(shù)據(jù)結(jié)構(gòu),但會保留FillType能曾。rewind會保留內(nèi)部的數(shù)據(jù)結(jié)構(gòu)度硝,但不保留FillType |
矩陣操作 | transform | 矩陣變換 |
(3)Paint配置
private void init(Context mContext){
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(10);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStyle(Paint.Style.STROKE);
}
這里需要注意的是:
- 如果繪制非閉合圖形,務(wù)必將畫筆設(shè)置成
Paint.Style.STROKE
描邊模式寿冕,否則繪制無效蕊程。 - 如果繪制閉合圖形,可以使用三種模式:
Paint.Style.STROKE
驼唱、Paint.Style.FILL
藻茂、Paint.Style.FILL_AND_STROKE
,分別是描邊模式、填充模式辨赐、描邊并填充模式优俘。
(4)moveTo和lineTo
lineTo(x, y)
:繪制一條執(zhí)行。
mPaint.setColor(Color.BLUE);
Path path = new Path();
path.lineTo(200, 200);
canvas.drawPath(path, mPaint);
lineTo
的兩個傳參是確定某一點的位置掀序,那么兩點確定一條直線
帆焕,這里還有一個點是什么呢?
如下圖所示森枪,
我們的畫筆默認(rèn)位置是(0, 0)视搏,如上圖所示审孽,(0, 0)的位置就在紅色矩形區(qū)域里面的小紅點位置县袱,也就是說,畫筆從(0, 0)到(200, 200)繪制直線佑力,這樣就滿足了兩點確定一條直線的理念
式散,繪制之后的效果圖如下:
Path
的moveTo
方法可以指定畫筆的位置,也就是下次繪制的開始位置打颤,下面我們結(jié)合moveTo
畫直線暴拄。我們現(xiàn)在畫一個假直角坐標(biāo)系,將畫筆位置移動到原點
编饺,并繪制直線乖篷。
mPaint.setColor(Color.BLACK);
//繪制一個假直角坐標(biāo)系
canvas.drawLine(0, 800, canvas.getWidth(), 800, mPaint);
canvas.drawLine(canvas.getWidth() / 2, 0, canvas.getWidth() / 2, canvas.getHeight(),mPaint);
mPaint.setColor(Color.BLUE);
Path path = new Path();
path.moveTo(canvas.getWidth() / 2, 800);
path.lineTo(200, 200);
canvas.drawPath(path, mPaint);
(5)moveTo
和setLastPoint
繪制兩條直線
mPaint.setColor(Color.BLUE);
Path path = new Path();
path.moveTo(canvas.getWidth() / 2, 800);
path.lineTo(200, 200);
path.moveTo(50, 200);
path.lineTo(100,600);
canvas.drawPath(path, mPaint);
代碼分析:
- 期初畫筆位置是(0, 0),執(zhí)行
path.moveTo(canvas.getWidth() / 2, 800)
之后畫筆位置變成了(canvas.getWidth() / 2, 800)透且; -
lineTo(200, 200):
繪制直線撕蔼,畫筆將從(canvas.getWidth() / 2, 800)開始畫直線,直到(200, 200)停下秽誊; -
moveTo(50, 200):
畫筆將從(200, 200)移動到(50, 200)鲸沮; -
lineTo(100,600):
繪制直線,畫筆將從(50, 200)開始畫直線锅论,直到(100,600)停下讼溺; -
drawPath:
開始繪制,這一步才開始繪制最易,前面的只是設(shè)置軌跡
而已怒坯。
效果圖:
下面開始解釋下setLastPoint
,setLastPoint
的意思就是重置最近一次畫筆位置藻懒。
mPaint.setColor(Color.BLUE);
Path path = new Path();
path.moveTo(canvas.getWidth() / 2, 800);
path.lineTo(200, 200);
path.setLastPoint(50, 200);
path.lineTo(100,600);
canvas.drawPath(path, mPaint);
代碼分析:
- 期初畫筆位置是(0, 0)敬肚,執(zhí)行
path.moveTo(canvas.getWidth() / 2, 800)
之后畫筆位置變成了(canvas.getWidth() / 2, 800); -
lineTo(200, 200):
繪制直線束析,畫筆將從(canvas.getWidth() / 2, 800)開始畫直線艳馒,直到(200, 200)停下; -
setLastPoint(50, 200):
此時畫筆的位置是(200, 200),setLastPoint
將重置畫筆的位置弄慰,使得上一次畫筆的位置變成了(50, 200)第美; -
lineTo(100,600):
繪制直線,畫筆將從(50, 200)開始畫直線陆爽,直到(100,600)停下什往; -
drawPath:
開始繪制,這一步才開始繪制慌闭,前面的只是設(shè)置軌跡
而已别威。
效果圖如下:
(6)close
Path有個close
方法,可以將第一個點和第二個點相連驴剔,形成閉合區(qū)域省古。
mPaint.setColor(Color.BLUE);
Path path = new Path();
path.moveTo(canvas.getWidth() / 2, 800);
path.lineTo(200, 200);
path.lineTo(100,600);
path.close();
canvas.drawPath(path, mPaint);
如圖所示
如果是Paint
的樣式修改成Paint.Style.FILL
或者Paint.Style.FILL_AND_STROKE
,那么可以不需要執(zhí)行close()
也可以達到閉合效果丧失。
(7)addXXX
系列
這些方法大致都是添加路勁(弧路徑
豺妓、圓路徑
、橢圓路徑
布讹、矩形路徑
琳拭、圓角矩形路徑
等等)
這里唯一需要說明的是Path.Direction.CW
、Path.Direction.CCW
描验。
一些方法中有個參數(shù):
Path.Direction.CW:
順時針
Path.Direction.CCW:
逆時針
我們來畫一個圓
順時針:
mPaint.setColor(Color.BLUE);
Path path = new Path();
path.addCircle(0, 0, 200, Path.Direction.CW);
canvas.drawPath(path, mPaint);
如圖:
畫筆起始點是(200, 0)白嘁,結(jié)束點是(0, -200)。
那么膘流, 我們可以利用setLastPoint
來畫一個桃子
path.setLastPoint(200, -200);
逆時針:
path.addCircle(0, 0, 200, Path.Direction.CCW);
起始點是(200, 0)絮缅,結(jié)束點是(0, 200),桃子在下面
path.setLastPoint(200, 200);
其它的路徑就不舉例了睡扬,總之盟蚣,如果是閉合區(qū)域,我們首先需要確定的是順時針還是逆時針卖怜,進而推敲出路徑的起始點和結(jié)束點屎开。
(8)addArc
與arcTo
addArc:
直接添加一個圓弧到path中
arcTo:
直接添加一個圓弧到path中,并且將當(dāng)前路徑的起始點和上一個路徑的結(jié)束點連接马靠。
arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
arcTo(RectF oval, float startAngle, float sweepAngle)
arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)
addArc
與arcTo
都是圓弧奄抽,這里主要展示一下它們的區(qū)別?
使用lineTo和addArc繪制直線和圓弧
path.lineTo(100, 100);
RectF rectF = new RectF();
rectF.left = 150;
rectF.top = 100;
rectF.right = 300;
rectF.bottom = 200;
path.addArc(rectF, 30,60);
canvas.drawPath(path, mPaint);
我們發(fā)現(xiàn)直線和圓弧互不相連∷現(xiàn)在將addArc
替換成arcTo
path.lineTo(100, 100);
RectF rectF = new RectF();
rectF.left = 150;
rectF.top = 100;
rectF.right = 300;
rectF.bottom = 200;
path.arcTo(rectF, 30, 60);
canvas.drawPath(path, mPaint);
我們發(fā)現(xiàn)逞度,當(dāng)前圓弧路徑的起始點和上一個路徑的終點相連了。
在方法里面有個參數(shù)forceMoveTo
妙啃,
true: 不相連档泽,相當(dāng)于addArc
false: 當(dāng)前圓弧路徑的起始點和上一個路徑的終點相連俊戳;
(9) computeBounds
計算path的邊界。
computeBounds(RectF bounds, boolean exact)
bounds:矩形邊界
exact:這個參數(shù)已經(jīng)沒用了
mPaint.setColor(Color.BLUE);
Path path = new Path();
path.lineTo(200, 200);
path.moveTo(-200, -200);
path.lineTo(-300,100);
RectF rectF = new RectF();
path.computeBounds(rectF, false);
path.addRect(rectF, Path.Direction.CW);
canvas.drawPath(path, mPaint);
效果圖如下:
代碼中畫了兩條線段馆匿,然后畫了一個矩形抑胎,但是奇怪的是,這個矩形沒有設(shè)置任何邊界大小渐北,computeBounds
是一個很有意思的方法阿逃,它可以自動計算兩條線段所在的邊界范圍,所以當(dāng)繪制這個矩形的時候其邊界不是(0,0,0,0)赃蛛,當(dāng)Path的軌跡所占點
數(shù)量為0或者1時恃锉,繪制這個矩形的時候其邊界是(0,0,0,0)。
(10)incReserve(int extraPtCount)
提示路徑以準(zhǔn)備添加更多點呕臂。這可以允許更有效地分配其存儲的路徑破托。
extraPtCount:
可以添加到這個的額外點數(shù)
(11)isEmpty()
判斷Path的路徑是否為空,如果Path沒有路徑诵闭,則說明Path的路徑是空的层释。
(12)isRect
判斷Path是否是矩形路徑扬蕊。
Path path = new Path();
path.lineTo(0, 0);
path.lineTo(200, 0);
path.lineTo(200, 200);
path.lineTo(0,200);
boolean isRect = path.isRect(rectF);
//path.addRect(rectF, Path.Direction.CW);
canvas.drawPath(path, mPaint);
和 computeBounds
有點類似窥淆,都是計算當(dāng)前路徑的邊界酱固,但是又和 computeBounds
不同:
-
isRect
只是判斷當(dāng)前Path的路徑是否是矩形帝璧; -
isRect
傳遞一個rectF參數(shù)群井,如果返回true妆够,則被計算之后的rectF和computeBounds
效果一樣炭臭,添加path.addRect(rectF, Path.Direction.CW)
同樣可以繪制出矩形邊界晤郑; -
isRect
傳遞一個rectF參數(shù)敌呈,如果返回false(Path的路徑非矩形),rectF大小就是(0,0,0,0)造寝,此時rectF將被忽略磕洪,如果這時再添加path.addRect(rectF, Path.Direction.CW)
,rectF將不再被忽略诫龙,rectF將被繪制出來析显,由于rectF的大小是(0,0,0,0),所以之前的非矩形路徑隨之被隱藏签赃。
(13)isConvex()
判斷曲線是否具有凸性谷异。
首先我們繪制兩條直線,兩條直線的結(jié)束點和起始點相連锦聊,如圖所示
其中(0,0)我們稱之為曲線的拐點
歹嘹,下面我們設(shè)置一下Path效果,讓這個曲線更像一個曲線吧
mPaint.setPathEffect(new CornerPathEffect(200));
定義:
如果曲線上任意兩點都在曲線的上
方孔庭,則這個曲線具有上凸
特性尺上。
我們再畫一個曲線,如下圖:
定義:
如果曲線上任意兩點都在曲線的下
方,則這個曲線具有下凸
特性怎抛。
再畫一個曲線仰税,如下圖:
像這樣的曲線既不滿足上凸
的特性,也不滿足下凸
的特性抽诉,所以該曲線沒有凸性
陨簇;
再畫一個,如下圖:
RectF rect = new RectF(0,0,400,400);
path.addRect(rect, Path.Direction.CCW);
那么這個矩形是否符合凸性
呢迹淌?
想要搞清楚這個問題河绽,必須搞清楚凸性的起點和終點,我們可以通過setLastPoint
方法來找出凸性的起點和終點唉窃。
第一次實驗:
RectF rect = new RectF(0,0,400,400);
path.addRect(rect, Path.Direction.CCW);
path.setLastPoint(100, 200);
由于矩形是按照逆時針
的方式繪制耙饰,并且setLastPoint
之后圖形變成上圖的樣子,那么可以證明纹份,圖形的起始點(0,0)苟跪,原來終點是(400,0),現(xiàn)在終點是(100,200)蔓涧,由于矩形是閉合區(qū)域
所以起點
和終點
相連之后形成了閉合圖形
件已,現(xiàn)在我們不讓它閉合
,擦除多余的部分:
此時元暴,該曲線完全符合上凸
特性篷扩,我們稱之為,當(dāng)矩形按照逆時針
繪制后的圖形茉盏,具有凸性
鉴未。
第二次試驗:
RectF rect = new RectF(0,0,400,400);
path.addRect(rect, Path.Direction.CW);
path.setLastPoint(200, 100);
由于矩形是按照順時針
的方式繪制,并且setLastPoint
之后圖形變成上圖的樣子鸠姨,那么可以證明铜秆,圖形的起始點(0,0),原來終點是(0,400)讶迁,現(xiàn)在終點是(200,100)连茧,由于矩形是閉合區(qū)域
所以起點
和終點
相連之后形成了閉合圖形
,現(xiàn)在我們不讓它閉合
添瓷,擦除多余的部分:
此時梅屉,該曲線完全符合下凸
特性,我們稱之為鳞贷,當(dāng)矩形按照順時針
繪制后的圖形坯汤,具有凸性
。
isConvex
總結(jié):
-
isConvex
是API 21新增的接口搀愧,其使用量也相對較少惰聂; - 判斷一個圖形是否具有
凸性
疆偿,并不是靠猜
,而是有方法的搓幌; - 需要對數(shù)學(xué)幾何的
上凸
和下凸
的特性具有一定的了解杆故; - 需要找到圖形的
起點
和終點
,如果是封閉區(qū)域溉愁,需要擦除起點
和終點
連接(path.close())
的區(qū)域处铛,讓圖形變成不再閉合
,最終判斷曲線是否符合凸性
拐揭; - 可以結(jié)合
setLastPoint
方法尋找圖形的起點
和終點
撤蟆; - 上面判斷圖形是否具有
凸性
的方法寫的很明白了,其它圖形(比如:圓)
也可通過這個方法判斷是否具有凸性
堂污。
(14)setFillType
和isInverseFillType
setFillType:
設(shè)置Path的填充類型家肯,指定內(nèi)部的計算方式;
其填充類型有:
FillType .WINDING:
填充每一個封閉路徑盟猖。
FillType .EVEN_ODD:
填充每個封閉路徑不重合的地方讨衣。
FillType .INVERSE_WINDING:
與WINDING
相反,WINDING
填充每個封閉路徑的內(nèi)部
式镐,而INVERSE_WINDING
填充每個封閉路徑的外部空間反镇。
FillType .INVERSE_EVEN_ODD:
與EVEN_ODD
相反,它填充封閉路徑之外的空間和路徑和路徑相交
的空間碟案。
演示這些填充類型之前愿险,需要將Paint的樣式改為填充模式
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
或者
mPaint.setStyle(Paint.Style.FILL);
FillType.WINDING
-
FillType.EVEN_ODD
FillType.INVERSE_WINDING
FillType.INVERSE_EVEN_ODD
isInverseFillType:
判斷是否為反轉(zhuǎn)填充類型颇蜡。
反轉(zhuǎn)填充類型有兩種FillType.INVERSE_WINDING
价说、FillType.INVERSE_EVEN_ODD
,我們看一下源碼
/**
* Returns true if the filltype is one of the INVERSE variants
*
* @return true if the filltype is one of the INVERSE variants
*/
public boolean isInverseFillType() {
final int ft = nGetFillType(mNativePath);
return (ft & FillType.INVERSE_WINDING.nativeInt) != 0;
}
核心語句是(ft & FillType.INVERSE_WINDING.nativeInt) != 0;
风秤,核心算法是按位與計算取值范圍
鳖目,按位與(&)
使數(shù)字分組:
第一組:
取值范圍是 20
第二組:
取值范圍是 [21,22)
第三組:
取值范圍是 [22,23)
第四組:
取值范圍是 [23,24)
依次類推...
算法特性:
同一范圍內(nèi)的兩數(shù)的&
運算,等于當(dāng)前范圍的最小數(shù)(比如5&6=4)
缤弦,不同范圍的&
運算結(jié)果為0领迈;
根據(jù)這個算法特性,我們再來看下代碼碍沐;
分析:
- 填充類型取值分別是:0,1,2,3
-
FillType.INVERSE_WINDING
的取值是2狸捅,FillType.INVERSE_EVEN_ODD
的取值是3,這兩個反轉(zhuǎn)填充類型的取值正好都在第二組范圍累提。(我想接下來不需要我解釋了吧)
(15)set(Path src)
將原有Path尘喝,替換為src。
(16)offset
將Path平移到指定點斋陪。
offset(float dx, float dy)
offset(float dx, float dy, @Nullable Path dst)
offset
有兩個方法朽褪。
方法一:
Path path = new Path();
path.addCircle(0,0,200, Path.Direction.CW);
path.offset(100, 100);
canvas.drawPath(path, mPaint);
效果如下:
方法二:
Path path = new Path();
path.addCircle(0,0,200, Path.Direction.CW);
Path path1 = new Path();
path.offset(100, 100, path1);
canvas.drawPath(path, mPaint);
canvas.drawPath(path1, mPaint);
效果如下:
(17)Op
op(Path path, Op op)
op(Path path1, Path path2, Op op)
組合兩條路徑時可以執(zhí)行的邏輯操作置吓。
邏輯操作有:
Op.DIFFERENCE:
從第一條路徑中減去第二條路徑。
Path path = new Path();
path.addCircle(-100,-100,200, Path.Direction.CW);
Path newPath = new Path();
newPath.addCircle(100,100,200, Path.Direction.CW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path.op(newPath, Path.Op.DIFFERENCE);
}
canvas.drawPath(path, mPaint);
Op.INTERSECT:
兩條路徑相交缔赠。
Path path = new Path();
path.addCircle(-100,-100,200, Path.Direction.CW);
Path newPath = new Path();
newPath.addCircle(100,100,200, Path.Direction.CW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path.op(newPath, Path.Op.INTERSECT);
}
canvas.drawPath(path, mPaint);
Op.UNION:
把這兩條路聯(lián)合起來衍锚。
Path path = new Path();
path.addCircle(-100,-100,200, Path.Direction.CW);
Path newPath = new Path();
newPath.addCircle(100,100,200, Path.Direction.CW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path.op(newPath, Path.Op.UNION);
}
canvas.drawPath(path, mPaint);
Op.XOR:
排他或兩條路。
Path path = new Path();
path.addCircle(-100,-100,200, Path.Direction.CW);
Path newPath = new Path();
newPath.addCircle(100,100,200, Path.Direction.CW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path.op(newPath, Path.Op.XOR);
}
canvas.drawPath(path, mPaint);
Op.REVERSE_DIFFERENCE:
從第二條路徑中減去第一條路徑嗤堰。
Path path = new Path();
path.addCircle(-100,-100,200, Path.Direction.CW);
Path newPath = new Path();
newPath.addCircle(100,100,200, Path.Direction.CW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path.op(newPath, Path.Op.REVERSE_DIFFERENCE);
}
canvas.drawPath(path, mPaint);
(18)reset()
和rewind()
將Path清空戴质。
reset:
不保留內(nèi)部數(shù)據(jù)結(jié)構(gòu),但會保留FillType踢匣。
rewind:
會保留內(nèi)部的數(shù)據(jù)結(jié)構(gòu)置森,但不保留FillType。
一般使用reset
符糊。
(19)toggleInverseFillType
切換填充規(guī)則(即原有規(guī)則與反向規(guī)則之間相互切換)
FillType .WINDING
與FillType .INVERSE_WINDING
相互切換凫海。
FillType .EVEN_ODD
與FillType .INVERSE_EVEN_ODD
相互切換。
(20)transform
transform(Matrix matrix)
transform(Matrix matrix, Path dst)
對Path進行矩陣操作男娄,我們就拿矩陣的旋轉(zhuǎn)操作來演示行贪,代碼如下:
mPaint.setColor(Color.BLUE);
Path path = new Path();
path.addCircle(100,100,200, Path.Direction.CW);
path.addCircle(-100,-100,200, Path.Direction.CW);
Matrix matrix = new Matrix();
matrix.setRotate(degrees);
path.transform(matrix);
canvas.drawPath(path, mPaint);
degrees = degrees + 2;
invalidate();
另外,第二個方法有個參數(shù)dst模闲,意思就是:Path在矩陣操作之后建瘫,在Path保存到dst對象。
(21)rMoveTo
和rLineTo
-
moveTo
和rMoveTo
的區(qū)別尸折?
moveTo:
移動的是畫筆的位置啰脚;
rMoveTo:
不僅移動畫筆的位置,而且直角坐標(biāo)系也隨之移動实夹,此時畫筆的位置相當(dāng)于沒有變化橄浓。
-
lineTo
和rLineTo
的區(qū)別?
moveTo:
移動的是畫筆的位置亮航;
rMoveTo:
不僅移動畫筆的位置荸实,而且直角坐標(biāo)系也隨之移動,此時畫筆的位置相當(dāng)于沒有變化缴淋。
(22)quadTo准给、cubicTo、rQuadTo重抖、rCubicTo
貝賽爾曲線是Path的一個非常重要的知識點露氮,我給它單獨整理了一篇文章,如下:
[本章完...]