1. 程式人生 > >Path類的最全面詳解

Path類的最全面詳解

前言

  • 自定義View是Android開發者必須瞭解的基礎;而Path類的使用在自定義View繪製中發揮著非常重要的作用
  • 網上有大量關於自定義View中Path類的文章,但存在一些問題:內容不全、思路不清晰、簡單問題複雜化等等
  • 今天,我將全面總結自定義View中Path類的使用,我能保證這是市面上的最全面、最清晰、最易懂的

目錄

目錄

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()
  • 二者區別:
    Paste_Image.png

例項介紹:(含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引數)如下
Paste_Image.png

舉例:

   // 為了方便觀察,平移座標系
        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是Android開發者必須瞭解的基礎;而Path類的使用在自定義View繪製中發揮著非常重要的作用 網上有大量關於自定義View中Path類的文章,但存在一些問題:內容不全、思路不清晰、簡單問題複雜化等等 今天,我將全面總結自定義View

Canvas全面

前言 自定義View是Android開發者必須瞭解的基礎;而Canvas類的使用在自定義View繪製中發揮著非常重要的作用 網上有大量關於自定義View中Canvas類的文章,但存在一些問題:內容不全、思路不清晰、簡單問題複雜化等等 今天,我將全面總結自定義

對python3中pathlib庫的Path的使用

原文連線   https://www.jb51.net/article/148789.htm   1.呼叫庫 ? 1

webview全面(一)瞭解官方文件

簡單介紹 WebView是手機中內建了一款高效能 webkit 核心瀏覽器,在 SDK 中封裝的一個元件。沒有提供位址列和導航欄,WebView只是單純的展示一個網頁介面。在開發中經常都會用到。 顯示和渲染Web頁面 直接使用html檔案(網路上或本地as

清晰易懂的UML圖與的關係

虛線箭頭指向依賴; 實線箭頭指向關聯; 虛線三角指向介面; 實線三角指向父類; 空心菱形能分離而獨立存在,是聚合; 實心菱形精密關聯不可分,是組合; 上面是UML的語法。 在畫類圖的時候,理清類和類之間的關係是重點。類的關係有泛化(Generalization)、實現(Realization

Android 自定義View之繪圖工具Canvas+Paint+Path(onDraw方法)基礎

本章節講述三個繪圖工具類Canvas(畫布),Paint(畫筆),Path(路徑) 1.Canvas(畫布)相關方法詳解 1.1. 方法:Canvas() 作用:建立一個空的畫布,可以使

Runtime

app 面向 default 才會 long con 方便 ast 一起 簡介 OC這門語言把很多事情從編譯和鏈接階段推遲到運行時處理。只要有可能,它就會采取動態運行時機制。這意味著這門語言不僅需要一個編譯器還需要一個運行時系統來執行這些編譯後的代碼。這個運行時系統相當於O

css中偽/偽元素

input 其他 中文 tro 網頁 單元 web link 語言 一、偽類和偽元素 偽類和偽元素都是用來修飾不在文檔樹中的部分,區別在於, 偽類用於當已有元素處於的某個狀態時,為其添加對應的樣式,這個狀態是根據用戶行為而動態變化的(如:hover/:active)。

php+Mysql分頁 和引用

echo padding 數字 進行 else if sub var min func 一下內容為專用於分頁的類以及具體的方法和解析。<?php class Page { private $total;

【Vue實戰之路】一、Vue-cli全面及進階操作。

image 腳本 js基礎 這一 命令執行 bsp row 編譯 服務器 全面的Vue-cli學習,這一篇就夠了! 一、下載 使用vue-cli前,需先安裝node.js,node的安裝就不贅述,不過在此需要註意: 1. node版本需在4.x以上,首推6.x以上版本

表單提交時編碼型enctype

表單提交 這不 algo inter 轉義 除了 如果 ctype target 很早以前,當還沒有前端這個概念的時候,我在寫表單提交完全不去理會表單數據的編碼,在action屬性裏寫好目標URL,剩下的啊交給瀏覽器吧~但是現在,更多時候我們都采用Ajax方式提交數據,這種

java型轉換(自動轉換和強制轉換)

代碼 oid 高精 log 相加 println 類型轉換詳解 範圍 void 自動轉換 class Hello { public static void main(String[] args) { //自動轉換 int a = 5; byte b = 6

iOS中、元、isa

exit argv tst eth program ram 通過 classname TE 類相信大家都知道是什麽,如果看過runtime的源碼或者看過相關的文章對isa肯定也不陌生,不過元類(meta class)大家可能就比較陌生了。不過大家也不要擔心,我會細細道來,讓

java集合之ArrayList

int() 相等 toa isempty ont ati urn 影響 輸入 一、ArrayList源碼分析 1、全局變量 (1)默認容量(主要是通過無參構造函數創建ArrayList時第一次add執行擴容操作時指定的elementData的數組容量為10) privat

java集合之LinkedList

list詳解 兩種 由於 list接口 add 不為 sel 結點 ESS 一、LinkedList簡介 由於LinkedList是一個實現了Deque的雙端隊列,所以LinkedList既可以當做Queue,又可以當做Stack,在將LinkedList當做Stack時,

GNS3全面系列-GNS3的前世今生

mach alt sof cci 局限性 圖形 客戶 text 拓撲 前言:我和“她”認識已經有十個春秋,3650個日起日落。5年前因為對她的“誤會”我們各奔東西,彼此擦肩而錯過;5年後由於個人發展原因再次與她重逢。最近由於工作項目上的需要,有了和她朝

JAVA的Random的用法

Random類 (java.util)         Random類中實現的隨機演算法是偽隨機,也就是有規則的隨機。在進行隨機時,隨機演算法的起源數字稱為種子數(seed),在種子數的基礎上進行一定的變換,從而產生

JAVA載入器

Java類載入器的作用就是在執行時載入類。Java類載入器基於三個機制:委託、可見性和單一性。委託機制是指將載入一個類的請求交給父類載入器,如果這個父類載入器不能夠找到或者載入這個類,那麼再載入它。可見性的原理是子類的載入器可以看見所有的父類載入器載入的類,而父類載入器看不到子類載入器載入的

java abstract 關鍵字 抽象方法和抽象的使用

java abstract 關鍵字 抽象類 抽象方法 的使用詳解 1.關鍵字 2.抽象類 3.抽象方法 4.abstract不能用來修飾屬性、構造器、private、final、static 1.關鍵字

SketchUp Pro 2018 新功能全面

作者 | 活力網Andrew 今天,我們來評測一下SketchUp Pro 2018的幾大新功能! 第一 更強大的剖切工具 SketchUp 增加剖面填充功能了!!! SU&LO 施工圖寶寶們應該都坐不住了!!! 首先 SU2017包括之前的版本 當我們點選剖面工具的時候