安卓自定義View進階:Path基本操作

一.Path常用方法表

爲了兼容性(偷懶) 本表格中去除了部分API21(即安卓版本5.0)以上才添加的方法。

作用 相關方法 備註
移動起點 moveTo 移動下一次操作的起點位置
設置終點 setLastPoint 重置當前path中最後一個點位置,如果在繪製之前調用,效果和moveTo相同
連接直線 lineTo 添加上一個點到當前點之間的直線到Path
閉合路徑 close 連接第一個點連接到最後一個點,形成一個閉合區域
添加內容 addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 添加(矩形, 圓角矩形, 橢圓, 圓, 路徑, 圓弧) 到當前Path (注意addArc和arcTo的區別)
是否爲空 isEmpty 判斷Path是否爲空
是否爲矩形 isRect 判斷path是否是一個矩形
替換路徑 set 用新的路徑替換到當前路徑所有內容
偏移路徑 offset 對當前路徑之前的操作進行偏移(不會影響之後的操作)
貝塞爾曲線 quadTo, cubicTo 分別爲二次和三次貝塞爾曲線的方法
rXxx方法 rMoveTo, rLineTo, rQuadTo, rCubicTo 不帶r的方法是基於原點的座標系(偏移量), rXxx方法是基於當前點座標系(偏移量)
填充模式 setFillType, getFillType, isInverseFillType, toggleInverseFillType 設置,獲取,判斷和切換填充模式
提示方法 incReserve 提示Path還有多少個點等待加入(這個方法貌似會讓Path優化存儲結構)
布爾操作(API19) op 對兩個Path進行布爾運算(即取交集、並集等操作)
計算邊界 computeBounds 計算Path的邊界
重置路徑 reset, rewind 清除Path中的內容
reset不保留內部數據結構,但會保留FillType.
rewind會保留內部的數據結構,但不保留FillType
矩陣操作 transform 矩陣變換

二.Path詳解

請關閉硬件加速,以免引起不必要的問題!
請關閉硬件加速,以免引起不必要的問題!
請關閉硬件加速,以免引起不必要的問題!

在AndroidMenifest文件中application節點下添上 android:hardwareAccelerated=”false”以關閉整個應用的硬件加速。
更多請參考這裏:Android的硬件加速及可能導致的問題

Path作用

本次特地開了一篇詳細講解Path,爲什麼要單獨摘出來呢,這是因爲Path在2D繪圖中是一個很重要的東西。

在前面我們講解的所有繪製都是簡單圖形(如 矩形 圓 圓弧等),而對於那些複雜一點的圖形則沒法去繪製(如繪製一個心形 正多邊形 五角星等),而使用Path不僅能夠繪製簡單圖形,也可以繪製這些比較複雜的圖形。另外,根據路徑繪製文本和剪裁畫布都會用到Path。

關於Path的作用先簡單地說這麼多,具體的我們接下來慢慢研究。

Path含義

官方介紹:

The Path class encapsulates compound (multiple contour) geometric paths consisting of straight line segments, quadratic curves, and cubic curves. It can be drawn with canvas.drawPath(path, paint), either filled or stroked (based on the paint’s Style), or it can be used for clipping or to draw text on a path.

嗯,沒錯依舊是拿來裝逼的,如果你看不懂的話,不用擔心,其實並沒有什麼卵用。

通俗解釋(sloop個人版):

Path是封裝了由直線和曲線(二次,三次貝塞爾曲線)構成的幾何路徑。你能用Canvas中的drawPath來把這條路徑畫出來(同樣支持Paint的不同繪製模式),也可以用於剪裁畫布和根據路徑繪製文字。我們有時會用Path來描述一個圖像的輪廓,所以也會稱爲輪廓線(輪廓線僅是Path的一種使用方法,兩者並不等價)

另外路徑有開放和封閉的區別。

圖像 名稱 備註
封閉路徑 首尾相接形成了一個封閉區域
開放路徑 沒有首位相接形成封閉區域

這個是我隨便畫的,僅爲展示一下區別,請無視我靈魂畫師一般的繪圖水準。

與Path相關的還有一些比較神奇的概念,不過暫且不說,等接下來需要用到的時候再詳細說明。

Path使用方法詳解

前面扯了一大堆概念性的東西。接下來就開始實戰了,請諸位看官準備好瓜子、花生、爆米花,坐下來慢慢觀看。

第1組: moveTo、 setLastPoint、 lineTo 和 close

由於Path的有些知識點無法單獨來講,所以本次採取了一次講一組方法。

按照慣例,先創建畫筆:

lineTo:

方法預覽:

首先講解的的LineTo,爲啥先講解這個呢?

是因爲moveTo、 setLastPoint、 close都無法直接看到效果,藉助有具現化效果的lineTo才能讓這些方法現出原形。

lineTo很簡單,只有一個方法,作用也很容易理解,line嘛,顧名思義就是一條線。

俗話(數學書上)說,兩點確定一條直線,但是看參數明顯只給了一個點的座標吧(這不按常理出牌啊)。

再仔細一看,這個lineTo除了line外還有一個to呢,to翻譯過來就是“至”,到某個地方的意思,lineTo難道是指從某個點到參數座標點之間連一條線?

沒錯,你猜對了,但是這某個點又是哪裏呢?

前面我們提到過Path可以用來描述一個圖像的輪廓,圖像的輪廓通常都是用一條線構成的,所以這裏的某個點就是上次操作結束的點,如果沒有進行過操作則默認點爲座標原點。

那麼我們就來試一下:

在示例中我們調用了兩次lineTo,第一次由於之前沒有過操作,所以默認點就是座標原點O,結果就是座標原點O到A(200,200)之間連直線(用藍色圈1標註)。

第二次lineTo的時候,由於上次的結束位置是A(200,200),所以就是A(200,200)到B(200,0)之間的連線(用藍色圈2標註)。

moveTo 和 setLastPoint:

方法預覽:

這兩個方法雖然在作用上有相似之處,但實際上卻是完全不同的兩個東東,具體參照下表:

方法名 簡介 是否影響之前的操作 是否影響之後操作
moveTo 移動下一次操作的起點位置
setLastPoint 設置之前操作的最後一個點位置

廢話不多說,直接上代碼:

這個和上面演示lineTo的方法類似,只不過在兩個lineTo之間添加了一個moveTo。

moveTo只改變下次操作的起點,在執行完第一次LineTo的時候,本來的默認點位置是A(200,200),但是moveTo將其改變成爲了C(200,100),所以在第二次調用lineTo的時候就是連接C(200,100) 到 B(200,0) 之間的直線(用藍色圈2標註)。

下面是setLastPoint的示例:

setLastPoint是重置上一次操作的最後一個點,在執行完第一次的lineTo的時候,最後一個點是A(200,200),而setLastPoint更改最後一個點爲C(200,100),所以在實際執行的時候,第一次的lineTo就不是從原點O到A(200,200)的連線了,而變成了從原點O到C(200,100)之間的連線了。

在執行完第一次lineTo和setLastPoint後,最後一個點的位置是C(200,100),所以在第二次調用lineTo的時候就是C(200,100) 到 B(200,0) 之間的連線(用藍色圈2標註)。

close

方法預覽:

close方法用於連接當前最後一個點和最初的一個點(如果兩個點不重合的話),最終形成一個封閉的圖形。

很明顯,兩個lineTo分別代表第1和第2條線,而close在此處的作用就算連接了B(200,0)點和圓的O之間的第3條線,使之形成一個封閉的圖形。

注意:close的作用是封閉路徑,與當前最後一個點和第一個點並不等價。如果連接了最後一個點和第一個點仍然無法形成封閉圖形,則close什麼 也不做。

第2組: addXxx與arcTo

這次內容主要是在Path中添加基本圖形,重點區分addArc與arcTo。

第一類(基本形狀)

方法預覽:

這一類就是在path中添加一個基本形狀,基本形狀部分和前面所講的繪製基本形狀並無太大差別,詳情參考Canvas(1)顏色與基本形狀, 本次只將其中不同的部分摘出來詳細講解一下。

仔細觀察一下第一類是方法,無一例外,在最後都有一個Path.Direction,這是一個什麼神奇的東東?

Direction的意思是 方向,趨勢。 點進去看一下會發現Direction是一個枚舉(Enum)類型,裏面只有兩個枚舉常量,如下:

類型解釋翻譯
CWclockwise順時針
CCWcounter-clockwise逆時針

瞬間懵逼,我只是想添加一個基本的形狀啊,搞什麼順時針和逆時針, (╯‵□′)╯︵┻━┻

稍安勿躁,┬─┬ ノ( ‘ – ‘ノ) {擺好擺好) 既然存在肯定是有用的,先偷偷劇透一下這個順時針和逆時針的作用。

序號作用
1在添加圖形時確定閉合順序(各個點的記錄順序)
2對圖形的渲染結果有影響(是判斷圖形渲染的重要條件)

這個先劇透這麼多,至於對閉合順序有啥影響,自相交圖形的渲染等問題等請慢慢看下去

咱們先研究確定閉合順序的問題,添加一個矩形試試看:

將上面代碼的CW改爲CCW再運行一次。接下來就是見證奇蹟的時刻,兩次運行結果一模一樣,有木有很神奇!

(╯°Д°)╯︵ ┻━┻(再TM掀一次) 坑人也不帶這樣的啊,一毛一樣要它幹嘛。

其實啊,這個東東是自帶隱身技能的,想要讓它現出原形,就要用到咱們剛剛學到的setLastPoint(重置當前最後一個點的位置)。

可以明顯看到,圖形發生了奇怪的變化。爲何會如此呢?

我們先分析一下,繪製一個矩形(僅繪製邊線),實際上只需要進行四次lineTo操作就行了,也就是說,只需要知道4個點的座標,然後使用moveTo到第一個點,之後依次lineTo就行了(從上面的測試可以看出,在實際繪製中也確實是這麼幹的)。

可是爲什麼要這麼做呢?確定一個矩形最少需要兩個點(對角線的兩個點),根據這兩個點的座標直接算出四條邊然後畫出來不就行了,幹嘛還要先計算出四個點座標,之後再連直線呢?

這個就要涉及一些path的存儲問題了,前面在path中的定義中說過,Path是封裝了由直線和曲線(二次,三次貝塞爾曲線)構成的幾何路徑。其中曲線部分用的是貝塞爾曲線,稍後再講。 然而除了曲線部分就只剩下直線了,對於直線的存儲最簡單的就是記錄座標點,然後直接連接各個點就行了。雖然記錄矩形只需要兩個點,但是如果只用兩個點來記錄一個矩形的話,就要額外增加一個標誌位來記錄這是一個矩形,顯然對於存儲和解析都是很不划算的事情,將矩形轉換爲直線,爲的就是存儲記錄方便。

扯了這麼多,該回歸正題了,就是我們的順時針和逆時針在這裏是幹啥的?

圖形在實際記錄中就是記錄各個的點,對於一個圖形來說肯定有多個點,既然有這麼多的點,肯定就需要一個先後順序,這裏順時針和逆時針就是用來確定記錄這些點的順序的。

對於上面這個矩形來說,我們採用的是順時針(CW),所以記錄的點的順序就是 A -> B -> C -> D. 最後一個點就是D,我們這裏使用setLastPoint改變最後一個點的位置實際上是改變了D的位置。

理解了上面的原理之後,設想如果我們將順時針改爲逆時針(CCW),則記錄點的順序應該就是 A -> D -> C -> B, 再使用setLastPoint則改變的是B的位置,我們試試看結果和我們的猜想是否一致:

通過驗證發現,發現結果和我們猜想的一樣,但是還有一個潛藏的問題不曉得大家可否注意到。我們用兩個點的座標確定了一個矩形,矩形起始點(A)就是我們指定的第一個點的座標。

需要注意的是,交換座標點的順序可能就會影響到某些繪製內容哦,例如上面的例子,你可以嘗試交換兩個座標點,或者指定另外兩個點來作爲參數,雖然指定的是同一個矩形,但實際繪製出來是不同的哦。

參數中點的順序很重要!
參數中點的順序很重要!
參數中點的順序很重要!

重要的話說三遍,本次是用矩形作爲例子的,其他的幾個圖形基本上都包含了曲線,詳情參見後續的貝塞爾曲線部分。

關於順時針和逆時針對圖形填充結果的影響請等待後續文章,雖然只講了一個Path,但也是內容頗多,放進一篇中就太長了,請見諒。

第二類(Path)

方法預覽:

這個相對比較簡單,也很容易理解,就是將兩個Path合併成爲一個。

第三個方法是將src添加到當前path之前先使用Matrix進行變換。

第二個方法比第一個方法多出來的兩個參數是將src進行了位移之後再添加進當前path中。

示例:

首先我們新建地方兩個Path(矩形和圓形)中心都是座標原點,我們在將包含圓形的path添加到包含矩形的path之前將其進行移動了一段距離,最終繪製出來的效果就如上面所示。

第三類(addArc與arcTo)

方法預覽:

從名字就可以看出,這兩個方法都是與圓弧相關的,作用都是添加一個圓弧到path中,但既然存在兩個方法,兩者之間肯定是有區別的:

名稱作用區別
addArc添加一個圓弧到path直接添加一個圓弧到path中
arcTo添加一個圓弧到path添加一個圓弧到path,如果圓弧的起點和上次最後一個座標點不相同,就連接兩個點

可以看到addArc有1個方法(實際上是兩個的,但另一個重載方法是API21添加的), 而arcTo有2個方法,其中一個最後多了一個布爾類型的變量forceMoveTo。

forceMoveTo是什麼作用呢?

這個變量意思爲“是否強制使用moveTo”,也就是說,是否使用moveTo將變量移動到圓弧的起點位移,也就意味着:

forceMoveTo含義等價方法
true將最後一個點移動到圓弧起點,即不連接最後一個點與圓弧起點public void addArc (RectF oval, float startAngle, float sweepAngle)
false不移動,而是連接最後一個點與圓弧起點public void arcTo (RectF oval, float startAngle, float sweepAngle)

示例(addArc):

示例(arcTo):

從上面兩張運行效果圖可以清晰的看出來兩者的區別,我就不再廢話了。

第3組:isEmpty、 isRect、isConvex、 set 和 offset

這一組比較簡單,稍微說一下就可以了。

isEmpty

方法預覽:

判斷path中是否包含內容。

log輸出結果:

isRect

方法預覽:

判斷path是否是一個矩形,如果是一個矩形的話,會將矩形的信息存放進參數rect中。

log 輸出結果:

set

方法預覽:

將新的path賦值到現有path。

offset

方法預覽:

這個的作用也很簡單,就是對path進行一段平移,它和Canvas中的translate作用很像,但Canvas作用於整個畫布,而path的offset只作用於當前path。

但是第二個方法最後怎麼會有一個path作爲參數?

其實第二個方法中最後的參數das是存儲平移後的path的。

dst狀態 效果
dst不爲空 將當前path平移後的狀態存入dst中,不會影響當前path
dat爲空(null) 平移將作用於當前path,相當於第一種方法

示例:

從運行效果圖可以看出,雖然我們在dst中添加了一個矩形,但是並沒有表現出來,所以,當dst中存在內容時,dst中原有的內容會被清空,而存放平移後的path。

三.總結

本想一篇把path寫完,但是萬萬沒想到居然扯了這麼多。本篇中講解的是直線部分和一些常用方法,下一篇將着重講解貝塞爾曲線和自相交圖形渲染等相關問題,敬請期待哦。

學完本篇之後又解鎖了新的境界,可以看看這位大神的文章 Android雷達圖(蜘蛛網圖)繪製

這個精小幹練,非常適合新手練習使用,幫助大家更好的熟悉path的使用。

(,,• ₃ •,,)

 

參考資料

Path
Canvas
android繪圖之Path總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章