1. 程式人生 > >Path類的最全面具體解釋 - 自己定義View應用系列

Path類的最全面具體解釋 - 自己定義View應用系列

選擇 思路 right i++ 點贊 empty get nes 易懂

前言

  • 自己定義View是Android開發人員必須了解的基礎;而Path類的使用在自己定義View繪制中發揮著很關鍵的數據
  • 網上有大量關於自己定義View中Path類的文章。但存在一些問題:內容不全、思路不清晰、簡單問題復雜化等等
  • 今天。我將全面總結自己定義View中Path類的使用,我能保證這是市面上的最全面、最清晰、最易懂的
  1. 文章較長,建議收藏等充足時間再進行閱讀
  2. 閱讀本文前請先閱讀自己定義View基礎 - 最易懂的自己定義View原理系列

文件夾

技術分享


1. 簡單介紹

  • 定義:路徑。即無數個點連起來的線
  • 作用:設置繪制的順序 & 區域

    Path僅僅用於描寫敘述順序 & 區域,單使用Path無法產生效果

  • 應用場景:繪制復雜圖形(如心形、五角星等等)

    Path類封裝了由直線和曲線(2、3次貝塞爾曲線)構成的幾何路徑。


2. 基礎

2.1 開放路徑與閉合路徑的差別

技術分享

2.2 怎樣推斷點在圖形內還是圖形外

  • 推斷方法分為奇偶規則 & 非零圍繞規則。詳細介紹例如以下:

技術分享

舉例說明1:(奇偶規則)
技術分享

由上圖知:

  • p1發出的射線與圖形相交1個點,即奇數點,所以P1點在圖形內
  • p2發出的射線與圖形相交2個點,即偶數點。所以P2點在圖形內

舉例說明2:(非零圍繞數規則)


從上面方法分析到,不論什麽圖形都是由點連成線組成的。是具備方向的,看下圖:(矩形是順時針)
技術分享

  • p1發出的射線與圖形相交1個點。矩形的右側線從左邊射到右邊,圍繞數-1,終於圍繞數為-1。故p1在圖形內部。
  • p2發出的射線與圖形相交2個點:矩形的右側邊從左邊射到右邊
    圍繞數-1。矩形的下側邊從右邊射到左邊,圍繞數+1。終於圍繞數為0.故p2在圖形外部

3. 詳細使用

3.1 對象創建

    // 使用Path首先要new一個Path對象
    // Path的起點默覺得坐標為(0,0)
    Path path = new Path();
    // 特別註意:建全局Path
對象,在onDraw()按需改動。盡量不要在onDraw()方法裏new對象 // 原因:若View頻繁刷新。就會頻繁創建對象,拖慢刷新速度。

3.2 詳細方法使用

由於path類的方法都是聯合使用。所以以下將一組組方法進行介紹。

第一組:設置路徑

採用moveTo()、setLastPoint()、lineTo()、close()組合


    // 設置當前點位置
    // 後面的路徑會從該點開始畫
    moveTo(float x, float y) ;

    // 當前點(上次操作結束的點)會連接該點
    // 假設沒有進行過操作則默認點為坐標原點。

lineTo(float x, float y) ; // 閉合路徑,即將當前點和起點連在一起 // 註:假設連接了最後一個點和第一個點仍然無法形成封閉圖形,則close什麽也不做 close() 。

  • 可使用setLastPoint()設置當前點位置(取代moveTo()
  • 二者差別:
    技術分享

實例介紹:(含setLastPoint()moveTo()


 // 使用moveTo()
 // 起點默認是(0,0)
        //連接點(400,500)
        path.lineTo(400, 500);

        // 將當前點移動到(300, 300)
        path.moveTo(300, 300) ;

        //連接點(900, 800)
        path.lineTo(900, 800);

        // 閉合路徑,即連接當前點和起點
        // 即連接(200,700)與起點2(300, 300)
        // 註:此時起點已經進行變換
        path.close();

        // 畫出路徑
        canvas.drawPath(path, mPaint1);

// 使用setLastPoint()
// 起點默認是(0,0)
        //連接點(400,500)
        path.lineTo(400, 500);

        // 將當前點移動到(300, 300)
        // 會影響之前的操作
        // 但不將此設置為新起點
        path.setLastPoint(300, 300) ;

        //連接點(900,800)
        path.lineTo(900, 800);

        //連接點(200,700)
        path.lineTo(200, 700);

        // 閉合路徑,即連接當前點和起點
        // 即連接(200,700)與起點(0,0)
        // 註:起點一直沒變化
        path.close();

        // 畫出路徑
        canvas.drawPath(path, mPaint1);

技術分享

關於重置路徑

  • 重置Path有兩個方法:reset()rewind()
  • 兩者差別在於:
方法 是否保留FillType設置 是否保留原有數據結構
Path.reset()
Path.rewind()
  1. FillType影響顯示效果。數據結構影響重建速度
  2. 所以一般選擇Path.reset()

由於較簡單,此處不作過多展示。

第二組: 加入路徑

採用addXxx()、arcTo()組合

2.1 加入基本圖形

  • 作用:在Path路徑中加入基本圖形

    如圓形路徑、圓弧路徑等等

  • 詳細使用


// 加入圓弧
// 方法1
public void addArc (RectF oval, float startAngle, float sweepAngle)

//  startAngle:確定角度的起始位置
//  sweepAngle : 確定掃過的角度

    // 方法2
    // 與上面方法唯一不同的是:假設圓弧的起點和上次最後一個坐標點不同樣,就連接兩個點
    public void arcTo (RectF oval, float startAngle, float sweepAngle)

   // 方法3
   // 參數forceMoveTo:是否將之前路徑的結束點設置為圓弧起點
   // true:在新的起點畫圓弧,不連接最後一個點與圓弧起點,即與之前路徑沒有交集(同addArc())
  // false:在新的起點畫圓弧。但會連接之前路徑的結束點與圓弧起點。即與之前路徑有交集(同arcTo(3參數))
    public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
// 以下會詳細說明


  // 加入圓形路徑
  // 起點:x軸正方向的0度
  // 當中參數dir:指定繪制時是順時針還是逆時針:CW為順時針,  CCW為逆時針
  // 路徑起點變為圓在X軸正方向最大的點
  addCircle(float x, float y, float radius, Path.Direction dir)   

   // 加入橢圓形路徑
  // 當中。參數oval作為橢圓的外切矩形區域
  addOval(RectF oval, Path.Direction dir)    

  // 加入矩形路徑
  // 路徑起點變為矩形的左上角頂點
  addRect(RectF rect, Path.Direction dir)     

  //加入圓角矩形路徑

  addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)      

//  註:加入圖形路徑後會改變路徑的起點

主要說一下dir這個參數:

dir = Direction = 圖形的方向。為枚舉類型:

  • CW:clockwise。順時針
  • CCW:counter-clockwise。逆時針

圖形的方向影響的是:

  • 加入圖形時確定閉合順序(各個點的記錄順序)
  • 圖形的渲染結果(是推斷圖形渲染的重要條件)

圖形繪制的本質:先畫點,再將點連接起來。

所以。點與點之間是存在一個先後順序的;順時針和逆時針用於確定這些點的順序。

以下實例將說明:

  // 為了方便觀察,平移坐標系
        canvas.translate(350, 500);
        // 順時針
        path.addRect(0, 0, 400, 400, Path.Direction.CW);

        // 逆時針
//        path.addRect(0,0,400,400, Path.Direction.CCW);
        canvas.drawPath(path,mPaint1);

技術分享

關於加入圖形路徑後會影響路徑的起點,實比例如以下:

  // 軌跡1
        // 將Canvas坐標系移到屏幕正中
           canvas.translate(400,500);

        // 起點是(0,0),連接點(-100,0)
            path.lineTo(-100,0);
        // 連接點(-100,200)
            path.lineTo(-100,200);
        // 連接點(200,200)
            path.lineTo(200,200);
        // 閉合路徑。即連接當前點和起點
        // 即連接(200,200)與起點是(0,0)
            path.close();

        // 畫出路徑
            canvas.drawPath(path,paint);
        // 詳細請看下圖


// 軌跡2
        // 將Canvas坐標系移到屏幕正中
            canvas.translate(400,500);

        // 起點是(0,0)。連接點(-100,0)
            path.lineTo(-100,0);
        // 畫圓:圓心=(0,0),半徑=100px
        // 此時路徑起點改變 = (0,100)(記為起點2)
        // 起點改變原則:新繪圖形在x軸正方向的最後一個坐標
        // 後面路徑的變化以這個點繼續下去
            path.addCircle(0,0,100, Path.Direction.CCW);

        // 起點為:(0,100),連接 (-100,200)
            path.lineTo(-100,200);
        // 連接 (200,200)
            path.lineTo(200,200);

        // 閉合路徑,即連接當前點和起點(註:閉合的是起點2)
        // 即連接(200,200)與起點2(0,100)
            path.close();

        // 畫出路徑
            canvas.drawPath(path,paint);

        // // 詳細請看下圖

技術分享

這裏著重說明:加入圓弧路徑(addArc與arcTo)

 // addArc
// 直接加入一個圓弧到path中
//  startAngle:確定角度的起始位置
//  sweepAngle : 確定掃過的角度
    public void addArc (RectF oval, float startAngle, float sweepAngle)


    // arcTo
    // 方法1
    // 同樣是加入一個圓弧到path
    // 與上面方法唯一不同的是:假設圓弧的起點和上次最後一個坐標點不同樣,就連接兩個點
    public void arcTo (RectF oval, float startAngle, float sweepAngle)

   // 方法2
   // 參數forceMoveTo:是否將之前路徑的結束點設置為圓弧起點
   // true:在新的起點畫圓弧,不連接最後一個點與圓弧起點,即與之前路徑沒有交集(同addArc())
  // false:在新的起點畫圓弧,但會連接之前路徑的結束點與圓弧起點。即與之前路徑有交集(同arcTo(3參數))
    public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)

詳細請看以下實例


// 將一個圓弧路徑加入到一條直線路徑裏

 // 為了方便觀察,平移坐標系
        canvas.translate(350, 500);

        // 先將原點(0,0)連接點(100,100)
        path.lineTo(50, 200);

// 加入圓弧路徑(2分之1圓弧)

        // 不連接最後一個點與圓弧起點
        path.addArc(new RectF(200, 200, 300, 300), 0, 180);
        // path.arcTo(oval,0,270,true);             // 與上面一句作用等價

        // 連接之前路徑的結束點與圓弧起點
        path.arcTo(new RectF(200, 200, 300, 300), 0, 180);
        // path.arcTo(oval,0,270,false);             // 與上面一句作用等價

        // 畫出路徑
        canvas.drawPath(path, mPaint1);

技術分享

2.2 加入路徑

  • 作用:合並路徑

    即將路徑1加到路徑2裏

  • 詳細使用

    // 方法1
    public void addPath (Path src)

    // 方法2
    // 先將src進行(x,y)位移之後再加入到當前path
    public void addPath (Path src, float dx, float dy)

    // 方法3
    // 先將src進行Matrix變換再加入到當前path
    public void addPath (Path src, Matrix matrix)


// 實例:合並矩形路徑和圓形路徑

     // 為了方便觀察,平移坐標系
        canvas.translate(350, 500);
     // 創建路徑的對象
        Path pathRect = new Path();
        Path  pathCircle = new Path();
        // 畫一個矩形路徑
        pathRect.addRect(-200, -200, 200, 200, Path.Direction.CW);
        // 畫一個圓形路徑
        pathCircle.addCircle(0, 0, 100, Path.Direction.CW);

        // 將圓形路徑移動(0,200),再加入到矩形路徑裏
        pathRect.addPath(pathCircle, 0, 200);

        // 繪制合並後的路徑
        canvas.drawPath(pathRect,mPaint1);

技術分享

第三組:推斷路徑屬性

  • 採用isEmpty()、 isRect()、isConvex()、 set() 和 offset()組合

  • 詳細使用:

// 推斷path中是否包括內容
 public boolean isEmpty ()
// 樣例:
Path path = new Path();
path.isEmpty();  //返回false

 path.lineTo(100,100); // 返回true


// 推斷path是否是一個矩形
// 假設是一個矩形的話,會將矩形的信息存放進參數rect中。
public boolean isRect (RectF 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);  // b返回ture,
        // rect存放矩形參數,詳細例如以下:
        // rect.left = 0
        // rect.top = 0
        // rect.right = 400
        // rect.bottom = 400



// 將新的路徑替代現有路徑
 public void set (Path src)

        // 實例
        // 設置一矩形路徑
        Path path = new Path();                     
        path.addRect(-200,-200,200,200, Path.Direction.CW);

        // 設置一圓形路徑
        Path src = new Path();                     
        src.addCircle(0,0,100, Path.Direction.CW);

        // 將圓形路徑取代矩形路徑
        path.set(src);      

        // 繪制圖形
        canvas.drawPath(path,mPaint);


// 平移路徑
// 與Canvas.translate ()平移畫布相似


// 方法1
// 參數x,y:平移位置
public void offset (float dx, float dy)

// 方法2
// 參數dst:存儲平移後的路徑狀態,但不影響當前path
// 可通過dst參數繪制存儲的路徑
        public void offset (float dx, float dy, Path dst)



 // 為了方便觀察,平移坐標系
        canvas.translate(350, 500);

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

        // 平移路徑並存儲平移後的狀態
        Path dst = new Path();
        path.offset(400, 0, dst);                     // 平移

        canvas.drawPath(path, mPaint1);               // 繪制path


        // 通過dst繪制平移後的圖形(紅色)
        mPaint1.setColor(Color.RED);      
        canvas.drawPath(dst,mPaint1);

技術分享

第四組:設置路徑填充顏色

  • 在Android中,有四種填充模式。詳細例如以下

    均封裝在Path類中

填充模式 介紹
EVEN_ODD 奇偶規則
INVERSE_EVEN_ODD 反奇偶規則
WINDING 非零圍繞數規則
INVERSE_WINDING 反非零圍繞數規則

請記住兩個填充規律:
從我之前的文章(1)自己定義View基礎 - 最易懂的自己定義View原理系列提到,圖形是存在方向的(繪圖 = 連接點成的線 = 有連接順序)。

技術分享

  • 詳細使用
// 設置填充規則
path.setFillType()
// 可填規則
// 1. EVEN_ODD:奇偶規則
// 2. INVERSE_EVEN_ODD:反奇偶規則
// 3. WINDING :非零圍繞數規則
// 4. INVERSE_WINDING:反非零圍繞數規則

// 理解奇偶規則和反奇偶規則:填充效果相反
// 舉例:對於一個矩形而言,使用奇偶規則會填充矩形內部,而使用反奇偶規則會填充矩形外部(以下會舉例說明)

// 獲取當前填充規則
path.getFillType()

// 推斷是否是反向(INVERSE)規則
path.isInverseFillType()

// 切換填充規則(即原有規則與反向規則之間相互切換)
path.toggleInverseFillType()

實例1:(奇偶規則)


 // 為了方便觀察,平移坐標系
        canvas.translate(350, 500);

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

        // 設置Path填充模式為 奇偶規則
        path.setFillType(Path.FillType.EVEN_ODD);

        // 反奇偶規則
        // path.setFillType(Path.FillType.INVERSE_EVEN_ODD);

        // 畫出路徑
        canvas.drawPath(path, mPaint1);

技術分享

舉例2:(非零圍繞規則)

    // 為了方便觀察,平移坐標系
        canvas.translate(550, 550);
        // 在路徑中加入大正方形
        // 逆時針
        path.addRect(-400, -400, 400, 400, Path.Direction.CCW);

        // 在路徑中加入小正方形
        // 順時針
//        path.addRect(-200, -200, 200, 200, Path.Direction.CW);
//          設置為逆時針
          path.addRect(-200, -200, 200, 200, Path.Direction.CCW);


        // 設置Path填充模式為非零圍繞規則
        path.setFillType(Path.FillType.WINDING);
        // 設置反非零圍繞數規則
        // path.setFillType(Path.FillType.INVERSE_WINDING);

        // 繪制Path
        canvas.drawPath(path, mPaint1);               

技術分享

第五組:布爾操作

  • 作用:兩個路徑Path之間的運算
  • 應用場景:用簡單的圖形通過特定規則合成相對復雜的圖形。

  • 詳細使用
// 方法1
    boolean op (Path path, Path.Op op)
// 舉例
// 對 path1 和 path2 運行布爾運算,運算方式由第二個參數指定
// 運算結果存入到path1中。
    path1.op(path2, Path.Op.DIFFERENCE);


// 方法2
    boolean op (Path path1, Path path2, Path.Op op)
  // 舉例
    // 對 path1 和 path2 運行布爾運算,運算方式由第三個參數指定
    // 運算結果存入到path3中。

path3.op(path1, path2, Path.Op.DIFFERENCE)

之間的運算方式(即Path.Op參數)例如以下
技術分享

舉例:

   // 為了方便觀察,平移坐標系
        canvas.translate(550, 550);

        // 畫兩個圓
        // 圓1:圓心 = (0,0),半徑 = 100
        // 圓2:圓心 = (50,0),半徑 = 100
        path1.addCircle(0, 0, 100, Path.Direction.CW);
        path2.addCircle(50, 0,100, Path.Direction.CW);

        // 取兩個路徑的異或集
        path1.op(path2, Path.Op.XOR);
        // 畫出路徑
        canvas.drawPath(path1, mPaint1);

技術分享


4. 貝賽爾曲線

  • 定義:計算曲線的數學公式
  • 作用:計算並表示曲線

    不論什麽一條曲線都能夠用貝塞爾曲線表示

  • 詳細使用:貝塞爾曲線可通過1數據點和若幹個控制點描寫敘述

  1. 數據點:指路徑的起始點和終止點;
  2. 控制點:決定了路徑的彎曲軌跡;
  3. n+1階貝塞爾曲線 = 有n個控制點。
  4. (1階 = 一條直線,高階能夠拆解為多條低階曲線)

Canvas提供了畫二階 & 三階貝塞爾曲線的方法。以下是詳細方法:


// 繪制二階貝塞爾曲線
//  (x1,y1)為控制點,(x2,y2)為終點
quadTo(float x1, float y1, float x2, float y2)
//  (x1,y1)為控制點距離起點的偏移量。(x2,y2)為終點距離起點的偏移量
rQuadTo(float x1, float y1, float x2, float y2)

// 繪制三階貝塞爾曲線
// (x1,y1),(x2,y2)為控制點,(x3,y3)為終點
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
// (x1,y1)。(x2,y2)為控制點距離起點的偏移量,(x3,y3)為終點距離起點的偏移量
rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)

此處僅僅簡單介紹貝塞爾曲線,想詳細理解能夠參考這篇文章。


5. 總結

  • 通過閱讀本文。相信你已經全面了解Path類的使用;
  • 假設希望繼續了解自己定義View的原理,請參考我寫的文章:
    自己定義View基礎 - 最易懂的自己定義View原理系列(1)
    自己定義View Measure過程 - 最易懂的自己定義View原理系列(2)
    自己定義View Layout過程 - 最易懂的自己定義View原理系列(3)
    自己定義View Draw過程- 最易懂的自己定義View原理系列(4)

  • 接下來,我將繼續對自己定義View的應用進行分析,有興趣的能夠繼續關註Carson_Ho的安卓開發筆記


請幫頂或評論點贊!

由於你們的贊同/鼓舞是我寫作的最大動力!

Path類的最全面具體解釋 - 自己定義View應用系列