安卓本身定義View進階-Path基本操做

版權聲明:本人所有文章均採用 [知識共享 署名-非商業性使用-禁止演繹 4.0 國際 許可協議] 轉載前請保證理解此協議,原文出處 :http://www.gcssloop.com/#blog https://blog.csdn.net/u013831257/article/details/50784565

Path之基本操做

做者微博: @GcsSloop

【本系列相關文章】

在上一篇Canvas之圖片文字中咱們瞭解了怎樣使用Canvas中繪製圖片文字。結合前幾篇文章,Canvas的基本操做已經幾乎相同完結了。然而Canvas不只僅具備這些主要的操做,還可以更加炫酷。本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出不少其它炫(zhuang)酷(B)的東東了。css


一.Path常常用法表

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

做用 相關方法 備註
移動起點 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具體解釋

請關閉硬件加速,以避免引發沒必要要的問題。
請關閉硬件加速,以避免引發沒必要要的問題。
請關閉硬件加速,以避免引發沒必要要的問題!
java

在AndroidMenifest文件裏application節點下添上 android:hardwareAccelerated=」false」以關閉整個應用的硬件加速。
不少其它請參考這裏:Android的硬件加速及可能致使的問題
android

Path做用

本次特意開了一篇具體解說Path,爲何要單獨摘出來呢,這是由於Path在2D畫圖中是一個很是重要的東西。git

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

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

Path含義

官方介紹:canvas

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.markdown

嗯,沒錯依然是拿來裝逼的,假設你看不懂的話,不用操心。事實上並無什麼卵用。數據結構

通俗解釋(sloop我的版):

Path是封裝了由直線和曲線(二次,三次貝塞爾曲線)構成的幾何路徑。

你能用Canvas中的drawPath來把這條路徑畫出來(一樣支持Paint的不一樣繪製模式),也可以用於剪裁畫布和依據路徑繪製文字。咱們有時會用Path來描寫敘述一個圖像的輪廓。因此也會稱爲輪廓線(輪廓線僅是Path的一種用法。二者並不等價)

另外路徑有開放和封閉的差異。

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

這個是我隨便畫的。僅爲展現一下差異,請無視我靈魂畫師通常的畫圖水準。

與Path相關的另外一些比較奇妙的概念,只是暫且不說,等接下來需要用到的時候再具體說明。

Path用法具體解釋

前面扯了一大堆概念性的東西。

接下來就開始實戰了。請諸位看官准備好瓜子、花生、爆米花。坐下來慢慢觀看。

第1組: moveTo、 setLastPoint、 lineTo 和 close

由於Path的有些知識點沒法單獨來說,因此本次採取了一次講一組方法。

依照慣例。先建立畫筆:

Paint mPaint = new Paint();             // 建立畫筆
        mPaint.setColor(Color.BLACK);           // 畫筆顏色 - 黑色
        mPaint.setStyle(Paint.Style.STROKE);    // 填充模式 - 描邊
        mPaint.setStrokeWidth(10);              // 邊框寬度 - 10

lineTo:

方法預覽:

public void lineTo (float x, float y)

首先解說的的LineTo。爲啥先解說這個呢?

是由於moveTo、 setLastPoint、 close都沒法直接看到效果。藉助有具現化效果的lineTo才幹讓這些方法現出原形。

lineTo很是簡單,僅僅有一個方法,做用也很是easy理解,line嘛。顧名思義就是一條線。

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

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

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

前面咱們提到過Path可以用來描寫敘述一個圖像的輪廓。圖像的輪廓一般都是用一條線構成的。因此這裏的某個點就是上次操做結束的點,假設沒有進行過操做則默認點爲座標原點。

那麼咱們就來試一下:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心(寬高數據在onSizeChanged中獲取)

        Path path = new Path();                     // 建立Path

        path.lineTo(200, 200);                      // lineTo
        path.lineTo(200,0);

        canvas.drawPath(path, mPaint);              // 繪製Path

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

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

moveTo 和 setLastPoint:

方法預覽:

// moveTo
        public void moveTo (float x, float y)

        // setLastPoint
        public void setLastPoint (float dx, float dy)

這兩個方法儘管在做用上有相似之處,但實際上倒是全然不一樣的兩個東東,具體參照下表:

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

廢話很少說,直接上代碼:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心

        Path path = new Path();                     // 建立Path

        path.lineTo(200, 200);                      // lineTo

        path.moveTo(200,100);                       // moveTo

        path.lineTo(200,0);                         // lineTo

        canvas.drawPath(path, mPaint);              // 繪製Path

這個和上面演示lineTo的方法相似,僅僅只是在兩個lineTo之間加入了一個moveTo。

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

如下是setLastPoint的演示樣例:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心

        Path path = new Path();                     // 建立Path

        path.lineTo(200, 200);                      // lineTo

        path.setLastPoint(200,100);                 // setLastPoint

        path.lineTo(200,0);                         // lineTo

        canvas.drawPath(path, mPaint);              // 繪製Path

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

方法預覽:

public void close ()

close方法用於鏈接當前最後一個點和最初的一個點(假設兩個點不重合的話)。終於造成一個封閉的圖形。

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心

        Path path = new Path();                     // 建立Path

        path.lineTo(200, 200);                      // lineTo

        path.lineTo(200,0);                         // lineTo

        path.close();                               // close

        canvas.drawPath(path, mPaint);              // 繪製Path

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

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

第2組: addXxx與arcTo

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

第一類(基本形狀)

方法預覽:

// 第一類(基本形狀)
    // 圓形
    public void addCircle (float x, float y, float radius, Path.Direction dir)
    // 橢圓
    public void addOval (RectF oval, Path.Direction dir)
    // 矩形
    public void addRect (float left, float top, float right, float bottom, Path.Direction dir)
    public void addRect (RectF rect, Path.Direction dir)
    // 圓角矩形
    public void addRoundRect (RectF rect, float[] radii, Path.Direction dir)
    public void addRoundRect (RectF rect, float rx, float ry, Path.Direction dir)

這一類就是在path中加入一個基本形狀,基本形狀部分和前面所講的繪製基本形狀並沒有太大差異,詳情參考Canvas(1)顏色與基本形狀, 本次僅僅將當中不一樣的部分摘出來具體解說一下。

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

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

類型 解釋 翻譯
CW clockwise 順時針
CCW counter-clockwise 逆時針

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

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

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

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

我們先研究肯定閉合順序的問題。加入一個矩形試試看:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心

        Path path = new Path();

        path.addRect(-200,-200,200,200, Path.Direction.CW);

        canvas.drawPath(path,mPaint);

將上面代碼的CW改成CCW再執行一次。接下來就是見證奇蹟的時刻,兩次執行結果如出一轍,有木有很是奇妙!

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

**

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

canvas.translate(mWidth / 2, mHeight / 2); // 移動座標系到屏幕中心

        Path path = new Path();

        path.addRect(-200,-200,200,200, Path.Direction.CW);

        path.setLastPoint(-300,300); // <-- 重置最後一個點的位置

        canvas.drawPath(path,mPaint);

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

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

但是爲何要這麼作呢?肯定一個矩形最少需要兩個點(對角線的兩個點),依據這兩個點的座標直接算出四條邊而後畫出來不便可了,幹嗎還要先計算出四個點座標,以後再連直線呢?

這個就要涉及一些path的存儲問題了。前面在path中的定義中說過。Path是封裝了由直線和曲線(二次,三次貝塞爾曲線)構成的幾何路徑。當中曲線部分用的是貝塞爾曲線,稍後再講。

然而除了曲線部分就僅僅剩下直線了。對於直線的存儲最簡單的就是記錄座標點,而後直接鏈接各個點便可了。

儘管記錄矩形僅僅需要兩個點,但是假設僅僅用兩個點來記錄一個矩形的話。就要額外添加一個標誌位來記錄這是一個矩形,顯然對於存儲和解析都是很是不划算的事情。將矩形轉換爲直線,爲的就是存儲記錄方便。

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

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

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

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

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心

        Path path = new Path();

        path.addRect(-200,-200,200,200, Path.Direction.CCW);

        path.setLastPoint(-300,300);                // <-- 重置最後一個點的位置

        canvas.drawPath(path,mPaint);

經過驗證發現,發現結果和咱們猜測的同樣。但是另外一個潛藏的問題不曉得你們能否注意到。

咱們用兩個點的座標肯定了一個矩形,矩形起始點(A)就是咱們指定的第一個點的座標。

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

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

重要的話說三遍。本次是用矩形做爲樣例的,其它的幾個圖形基本上都包括了曲線,詳情參見興許的貝塞爾曲線部分。

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

第二類(Path)

方法預覽:

// 第二類(Path)
    // path
    public void addPath (Path src)
    public void addPath (Path src, float dx, float dy)
    public void addPath (Path src, Matrix matrix)

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

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

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

演示樣例:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心
        canvas.scale(1,-1);                         // <-- 注意 翻轉y座標軸

        Path path = new Path();
        Path src = new Path();

        path.addRect(-200,-200,200,200, Path.Direction.CW);
        src.addCircle(0,0,100, Path.Direction.CW);

        path.addPath(src,0,200);

        mPaint.setColor(Color.BLACK);           // 繪製合併後的路徑
        canvas.drawPath(path,mPaint);

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

第三類(addArc與arcTo)

方法預覽:

// 第三類(addArc與arcTo)
    // addArc
    public void addArc (RectF oval, float startAngle, float sweepAngle)
    // arcTo
    public void arcTo (RectF oval, float startAngle, float sweepAngle)
    public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)

從名字就可以看出,這兩個方法都是與圓弧相關的,做用都是加入一個圓弧到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):

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心
        canvas.scale(1,-1);                         // <-- 注意 翻轉y座標軸

        Path path = new Path();
        path.lineTo(100,100);

        RectF oval = new RectF(0,0,300,300);

        path.addArc(oval,0,270);
        // path.arcTo(oval,0,270,true); // <-- 和上面一句做用等價

        canvas.drawPath(path,mPaint);

演示樣例(arcTo):

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心
        canvas.scale(1,-1);                         // <-- 注意 翻轉y座標軸

        Path path = new Path();
        path.lineTo(100,100);

        RectF oval = new RectF(0,0,300,300);

        path.arcTo(oval,0,270);
        // path.arcTo(oval,0,270,false); // <-- 和上面一句做用等價

        canvas.drawPath(path,mPaint);

從上面兩張執行效果圖可以清晰的看出來二者的差異。我就再也不廢話了。

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

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

isEmpty

方法預覽:

public boolean isEmpty ()

推斷path中是否包括內容。

Path path = new Path();
        Log.e("1",path.isEmpty()+"");

        path.lineTo(100,100);
        Log.e("2",path.isEmpty()+"");

log輸出結果:

03-02 14:22:54.770 12379-12379/com.sloop.canvas E/1: true
03-02 14:22:54.770 12379-12379/com.sloop.canvas E/2: false

isRect

方法預覽:

public boolean isRect (RectF rect)

推斷path是不是一個矩形,假設是一個矩形的話。會將矩形的信息存放進參數rect中。

path.lineTo(0,400);
        path.lineTo(400,400);
        path.lineTo(400,0);
        path.lineTo(0,0);

        RectF rect = new RectF();
        boolean b = path.isRect(rect);
        Log.e("Rect","isRect:"+b+"| left:"+rect.left+"| top:"+rect.top+"| right:"+rect.right+"| bottom:"+rect.bottom);

log 輸出結果:

03-02 16:48:39.669 24179-24179/com.sloop.canvas E/Rect: isRect:true| left:0.0| top:0.0| right:400.0| bottom:400.0

set

方法預覽:

public void set (Path src)

將新的path賦值到現有path。

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心
        canvas.scale(1,-1);                         // <-- 注意 翻轉y座標軸

        Path path = new Path();                     // path加入一個矩形
        path.addRect(-200,-200,200,200, Path.Direction.CW);

        Path src = new Path();                      // src加入一個圓
        src.addCircle(0,0,100, Path.Direction.CW);

        path.set(src);                              // 大體至關於 path = src;

        canvas.drawPath(path,mPaint);

offset

方法預覽:

public void offset (float dx, float dy)
        public void offset (float dx, float dy, Path dst)

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

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

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

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

演示樣例:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動座標系到屏幕中心
        canvas.scale(1,-1);                         // <-- 注意 翻轉y座標軸

        Path path = new Path();                     // path中加入一個圓形(圓心在座標原點)
        path.addCircle(0,0,100, Path.Direction.CW);

        Path dst = new Path();                      // dst中加入一個矩形
        dst.addRect(-200,-200,200,200, Path.Direction.CW);

        path.offset(300,0,dst);                     // 平移

        canvas.drawPath(path,mPaint);               // 繪製path

        mPaint.setColor(Color.BLUE);                // 更改畫筆顏色

        canvas.drawPath(dst,mPaint);                // 繪製dst

從執行效果圖可以看出。儘管咱們在dst中加入了一個矩形,但是並無表現出來,因此,當dst中存在內容時,dst中原有的內容會被清空。而存放平移後的path。

三.總結

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

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

這個精小幹練,很是適合新手練習使用,幫助你們更好的熟悉path的使用。

(,,• ₃ •,,)

PS: 由於本人水平有限,某些地方可能存在誤解或不許確。假設你對此有疑問可以提交Issues進行反饋。

About Me

做者微博: @GcsSloop

參考資料

Path

Canvas

android畫圖之Path總結

相關文章
相關標籤/搜索