
前言
- 自定義View是Android開發(fā)者必須了解的基礎(chǔ);而Path類的使用在自定義View繪制中發(fā)揮著非常重要的作用
- 網(wǎng)上有大量關(guān)于自定義View中Path類的文章,但存在一些問題:內(nèi)容不全、思路不清晰、簡(jiǎn)單問題復(fù)雜化等等
- 今天,我將全面總結(jié)自定義View中Path類的使用,我能保證這是市面上的最全面、最清晰、最易懂的
Carson帶你學(xué)Android自定義View文章系列:
Carson帶你學(xué)Android:自定義View基礎(chǔ)
Carson帶你學(xué)Android:一文梳理自定義View工作流程
Carson帶你學(xué)Android:自定義View繪制準(zhǔn)備-DecorView創(chuàng)建
Carson帶你學(xué)Android:自定義View Measure過程
Carson帶你學(xué)Android:自定義View Layout過程
Carson帶你學(xué)Android:自定義View Draw過程
Carson帶你學(xué)Android:手把手教你寫一個(gè)完整的自定義View
Carson帶你學(xué)Android:Canvas類全面解析
Carson帶你學(xué)Android:Path類全面解析
目錄

1. 簡(jiǎn)介
- 定義:路徑,即無(wú)數(shù)個(gè)點(diǎn)連起來的線
- 作用:設(shè)置繪制的順序 & 區(qū)域
Path只用于描述順序 & 區(qū)域,單使用Path無(wú)法產(chǎn)生效果
- 應(yīng)用場(chǎng)景:繪制復(fù)雜圖形(如心形、五角星等等)
Path類封裝了由直線和曲線(2、3次貝塞爾曲線)構(gòu)成的幾何路徑。
2. 基礎(chǔ)
2.1 開放路徑與閉合路徑的區(qū)別

2.2 如何判斷點(diǎn)在圖形內(nèi)還是圖形外
- 判斷方法分為奇偶規(guī)則 & 非零環(huán)繞規(guī)則,具體介紹如下:

-
舉例說明1:(奇偶規(guī)則)
示意圖
由上圖知:
- p1發(fā)出的射線與圖形相交1個(gè)點(diǎn),即奇數(shù)點(diǎn),所以P1點(diǎn)在圖形內(nèi)
- p2發(fā)出的射線與圖形相交2個(gè)點(diǎn),即偶數(shù)點(diǎn),所以P2點(diǎn)在圖形內(nèi)
-
舉例說明2:(非零環(huán)繞數(shù)規(guī)則)
從上面方法分析到,任何圖形都是由點(diǎn)連成線組成的,是具備方向的,看下圖:(矩形是順時(shí)針)
示意圖 - p1發(fā)出的射線與圖形相交1個(gè)點(diǎn),矩形的右側(cè)線從左邊射到右邊,環(huán)繞數(shù)-1,最終環(huán)繞數(shù)為-1,故p1在圖形內(nèi)部。
- p2發(fā)出的射線與圖形相交2個(gè)點(diǎn):矩形的右側(cè)邊從左邊射到右邊
環(huán)繞數(shù)-1;矩形的下側(cè)邊從右邊射到左邊,環(huán)繞數(shù)+1,最終環(huán)繞數(shù)為0.故p2在圖形外部
3. 具體使用
3.1 對(duì)象創(chuàng)建
// 使用Path首先要new一個(gè)Path對(duì)象
// Path的起點(diǎn)默認(rèn)為坐標(biāo)為(0,0)
Path path = new Path();
// 特別注意:建全局Path對(duì)象,在onDraw()按需修改;盡量不要在onDraw()方法里new對(duì)象
// 原因:若View頻繁刷新,就會(huì)頻繁創(chuàng)建對(duì)象,拖慢刷新速度。
3.2 具體方法使用
因?yàn)閜ath類的方法都是聯(lián)合使用,所以下面將一組組方法進(jìn)行介紹。
第一組:設(shè)置路徑
采用moveTo()、setLastPoint()、lineTo()、close()組合
// 設(shè)置當(dāng)前點(diǎn)位置
// 后面的路徑會(huì)從該點(diǎn)開始畫
moveTo(float x, float y) ;
// 當(dāng)前點(diǎn)(上次操作結(jié)束的點(diǎn))會(huì)連接該點(diǎn)
// 如果沒有進(jìn)行過操作則默認(rèn)點(diǎn)為坐標(biāo)原點(diǎn)。
lineTo(float x, float y) ;
// 閉合路徑,即將當(dāng)前點(diǎn)和起點(diǎn)連在一起
// 注:如果連接了最后一個(gè)點(diǎn)和第一個(gè)點(diǎn)仍然無(wú)法形成封閉圖形,則close什么也不做
close() ;
- 可使用
setLastPoint()設(shè)置當(dāng)前點(diǎn)位置(代替moveTo()) -
二者區(qū)別:
Paste_Image.png
實(shí)例介紹:(含setLastPoint()與moveTo())
// 使用moveTo()
// 起點(diǎn)默認(rèn)是(0,0)
//連接點(diǎn)(400,500)
path.lineTo(400, 500);
// 將當(dāng)前點(diǎn)移動(dòng)到(300, 300)
path.moveTo(300, 300) ;
//連接點(diǎn)(900, 800)
path.lineTo(900, 800);
// 閉合路徑,即連接當(dāng)前點(diǎn)和起點(diǎn)
// 即連接(200,700)與起點(diǎn)2(300, 300)
// 注:此時(shí)起點(diǎn)已經(jīng)進(jìn)行變換
path.close();
// 畫出路徑
canvas.drawPath(path, mPaint1);
// 使用setLastPoint()
// 起點(diǎn)默認(rèn)是(0,0)
//連接點(diǎn)(400,500)
path.lineTo(400, 500);
// 將當(dāng)前點(diǎn)移動(dòng)到(300, 300)
// 會(huì)影響之前的操作
// 但不將此設(shè)置為新起點(diǎn)
path.setLastPoint(300, 300) ;
//連接點(diǎn)(900,800)
path.lineTo(900, 800);
//連接點(diǎn)(200,700)
path.lineTo(200, 700);
// 閉合路徑,即連接當(dāng)前點(diǎn)和起點(diǎn)
// 即連接(200,700)與起點(diǎn)(0,0)
// 注:起點(diǎn)一直沒變化
path.close();
// 畫出路徑
canvas.drawPath(path, mPaint1);

關(guān)于重置路徑
- 重置Path有兩個(gè)方法:
reset()和rewind() - 兩者區(qū)別在于:
| 方法 | 是否保留FillType設(shè)置 | 是否保留原有數(shù)據(jù)結(jié)構(gòu) |
|---|---|---|
| Path.reset() | 是 | 否 |
| Path.rewind() | 否 | 是 |
FillType影響顯示效果;數(shù)據(jù)結(jié)構(gòu)影響重建速度- 所以一般選擇
Path.reset()
由于較簡(jiǎn)單,此處不作過多展示。
第二組: 添加路徑
采用addXxx()、arcTo()組合
2.1 添加基本圖形
- 作用:在Path路徑中添加基本圖形
如圓形路徑、圓弧路徑等等
- 具體使用
// 添加圓弧
// 方法1
public void addArc (RectF oval, float startAngle, float sweepAngle)
// startAngle:確定角度的起始位置
// sweepAngle : 確定掃過的角度
// 方法2
// 與上面方法唯一不同的是:如果圓弧的起點(diǎn)和上次最后一個(gè)坐標(biāo)點(diǎn)不相同,就連接兩個(gè)點(diǎn)
public void arcTo (RectF oval, float startAngle, float sweepAngle)
// 方法3
// 參數(shù)forceMoveTo:是否將之前路徑的結(jié)束點(diǎn)設(shè)置為圓弧起點(diǎn)
// true:在新的起點(diǎn)畫圓弧,不連接最后一個(gè)點(diǎn)與圓弧起點(diǎn),即與之前路徑?jīng)]有交集(同addArc())
// false:在新的起點(diǎn)畫圓弧,但會(huì)連接之前路徑的結(jié)束點(diǎn)與圓弧起點(diǎn),即與之前路徑有交集(同arcTo(3參數(shù)))
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
// 下面會(huì)詳細(xì)說明
// 加入圓形路徑
// 起點(diǎn):x軸正方向的0度
// 其中參數(shù)dir:指定繪制時(shí)是順時(shí)針還是逆時(shí)針:CW為順時(shí)針, CCW為逆時(shí)針
// 路徑起點(diǎn)變?yōu)閳A在X軸正方向最大的點(diǎn)
addCircle(float x, float y, float radius, Path.Direction dir)
// 加入橢圓形路徑
// 其中,參數(shù)oval作為橢圓的外切矩形區(qū)域
addOval(RectF oval, Path.Direction dir)
// 加入矩形路徑
// 路徑起點(diǎn)變?yōu)榫匦蔚淖笊辖琼旤c(diǎn)
addRect(RectF rect, Path.Direction dir)
//加入圓角矩形路徑
addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)
// 注:添加圖形路徑后會(huì)改變路徑的起點(diǎn)
主要說一下dir這個(gè)參數(shù):
dir = Direction = 圖形的方向,為枚舉類型:
- CW:clockwise,順時(shí)針
- CCW:counter-clockwise,逆時(shí)針
圖形的方向影響的是:
- 添加圖形時(shí)確定閉合順序(各個(gè)點(diǎn)的記錄順序)
- 圖形的渲染結(jié)果(是判斷圖形渲染的重要條件)
圖形繪制的本質(zhì):先畫點(diǎn),再將點(diǎn)連接起來。所以,點(diǎn)與點(diǎn)之間是存在一個(gè)先后順序的;順時(shí)針和逆時(shí)針用于確定這些點(diǎn)的順序。
下面實(shí)例將說明:
// 為了方便觀察,平移坐標(biāo)系
canvas.translate(350, 500);
// 順時(shí)針
path.addRect(0, 0, 400, 400, Path.Direction.CW);
// 逆時(shí)針
// path.addRect(0,0,400,400, Path.Direction.CCW);
canvas.drawPath(path,mPaint1);

關(guān)于加入圖形路徑后會(huì)影響路徑的起點(diǎn),實(shí)例如下:
// 軌跡1
// 將Canvas坐標(biāo)系移到屏幕正中
canvas.translate(400,500);
// 起點(diǎn)是(0,0),連接點(diǎn)(-100,0)
path.lineTo(-100,0);
// 連接點(diǎn)(-100,200)
path.lineTo(-100,200);
// 連接點(diǎn)(200,200)
path.lineTo(200,200);
// 閉合路徑,即連接當(dāng)前點(diǎn)和起點(diǎn)
// 即連接(200,200)與起點(diǎn)是(0,0)
path.close();
// 畫出路徑
canvas.drawPath(path,paint);
// 具體請(qǐng)看下圖
// 軌跡2
// 將Canvas坐標(biāo)系移到屏幕正中
canvas.translate(400,500);
// 起點(diǎn)是(0,0),連接點(diǎn)(-100,0)
path.lineTo(-100,0);
// 畫圓:圓心=(0,0),半徑=100px
// 此時(shí)路徑起點(diǎn)改變 = (0,100)(記為起點(diǎn)2)
// 起點(diǎn)改變?cè)瓌t:新畫圖形在x軸正方向的最后一個(gè)坐標(biāo)
// 后面路徑的變化以這個(gè)點(diǎn)繼續(xù)下去
path.addCircle(0,0,100, Path.Direction.CCW);
// 起點(diǎn)為:(0,100),連接 (-100,200)
path.lineTo(-100,200);
// 連接 (200,200)
path.lineTo(200,200);
// 閉合路徑,即連接當(dāng)前點(diǎn)和起點(diǎn)(注:閉合的是起點(diǎn)2)
// 即連接(200,200)與起點(diǎn)2(0,100)
path.close();
// 畫出路徑
canvas.drawPath(path,paint);
// // 具體請(qǐng)看下圖

這里著重說明:添加圓弧路徑(addArc與arcTo)
// addArc
// 直接添加一個(gè)圓弧到path中
// startAngle:確定角度的起始位置
// sweepAngle : 確定掃過的角度
public void addArc (RectF oval, float startAngle, float sweepAngle)
// arcTo
// 方法1
// 同樣是添加一個(gè)圓弧到path
// 與上面方法唯一不同的是:如果圓弧的起點(diǎn)和上次最后一個(gè)坐標(biāo)點(diǎn)不相同,就連接兩個(gè)點(diǎn)
public void arcTo (RectF oval, float startAngle, float sweepAngle)
// 方法2
// 參數(shù)forceMoveTo:是否將之前路徑的結(jié)束點(diǎn)設(shè)置為圓弧起點(diǎn)
// true:在新的起點(diǎn)畫圓弧,不連接最后一個(gè)點(diǎn)與圓弧起點(diǎn),即與之前路徑?jīng)]有交集(同addArc())
// false:在新的起點(diǎn)畫圓弧,但會(huì)連接之前路徑的結(jié)束點(diǎn)與圓弧起點(diǎn),即與之前路徑有交集(同arcTo(3參數(shù)))
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
具體請(qǐng)看下面實(shí)例
// 將一個(gè)圓弧路徑添加到一條直線路徑里
// 為了方便觀察,平移坐標(biāo)系
canvas.translate(350, 500);
// 先將原點(diǎn)(0,0)連接點(diǎn)(100,100)
path.lineTo(50, 200);
// 添加圓弧路徑(2分之1圓弧)
// 不連接最后一個(gè)點(diǎn)與圓弧起點(diǎn)
path.addArc(new RectF(200, 200, 300, 300), 0, 180);
// path.arcTo(oval,0,270,true); // 與上面一句作用等價(jià)
// 連接之前路徑的結(jié)束點(diǎn)與圓弧起點(diǎn)
path.arcTo(new RectF(200, 200, 300, 300), 0, 180);
// path.arcTo(oval,0,270,false); // 與上面一句作用等價(jià)
// 畫出路徑
canvas.drawPath(path, mPaint1);

2.2 添加路徑
- 作用:合并路徑
即將路徑1加到路徑2里
- 具體使用
// 方法1
public void addPath (Path src)
// 方法2
// 先將src進(jìn)行(x,y)位移之后再添加到當(dāng)前path
public void addPath (Path src, float dx, float dy)
// 方法3
// 先將src進(jìn)行Matrix變換再添加到當(dāng)前path
public void addPath (Path src, Matrix matrix)
// 實(shí)例:合并矩形路徑和圓形路徑
// 為了方便觀察,平移坐標(biāo)系
canvas.translate(350, 500);
// 創(chuàng)建路徑的對(duì)象
Path pathRect = new Path();
Path pathCircle = new Path();
// 畫一個(gè)矩形路徑
pathRect.addRect(-200, -200, 200, 200, Path.Direction.CW);
// 畫一個(gè)圓形路徑
pathCircle.addCircle(0, 0, 100, Path.Direction.CW);
// 將圓形路徑移動(dòng)(0,200),再添加到矩形路徑里
pathRect.addPath(pathCircle, 0, 200);
// 繪制合并后的路徑
canvas.drawPath(pathRect,mPaint1);

第三組:判斷路徑屬性
采用
isEmpty()、 isRect()、isConvex()、 set() 和 offset()組合具體使用:
// 判斷path中是否包含內(nèi)容
public boolean isEmpty ()
// 例子:
Path path = new Path();
path.isEmpty(); //返回false
path.lineTo(100,100); // 返回true
// 判斷path是否是一個(gè)矩形
// 如果是一個(gè)矩形的話,會(huì)將矩形的信息存放進(jìn)參數(shù)rect中。
public boolean isRect (RectF rect)
// 實(shí)例
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存放矩形參數(shù),具體如下:
// rect.left = 0
// rect.top = 0
// rect.right = 400
// rect.bottom = 400
// 將新的路徑替代現(xiàn)有路徑
public void set (Path src)
// 實(shí)例
// 設(shè)置一矩形路徑
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
// 設(shè)置一圓形路徑
Path src = new Path();
src.addCircle(0,0,100, Path.Direction.CW);
// 將圓形路徑代替矩形路徑
path.set(src);
// 繪制圖形
canvas.drawPath(path,mPaint);
// 平移路徑
// 與Canvas.translate ()平移畫布類似
// 方法1
// 參數(shù)x,y:平移位置
public void offset (float dx, float dy)
// 方法2
// 參數(shù)dst:存儲(chǔ)平移后的路徑狀態(tài),但不影響當(dāng)前path
// 可通過dst參數(shù)繪制存儲(chǔ)的路徑
public void offset (float dx, float dy, Path dst)
// 為了方便觀察,平移坐標(biāo)系
canvas.translate(350, 500);
// path中添加一個(gè)圓形(圓心在坐標(biāo)原點(diǎn))
path = new Path();
path.addCircle(0, 0, 100, Path.Direction.CW);
// 平移路徑并存儲(chǔ)平移后的狀態(tài)
Path dst = new Path();
path.offset(400, 0, dst); // 平移
canvas.drawPath(path, mPaint1); // 繪制path
// 通過dst繪制平移后的圖形(紅色)
mPaint1.setColor(Color.RED);
canvas.drawPath(dst,mPaint1);

第四組:設(shè)置路徑填充顏色
- 在Android中,有四種填充模式,具體如下
均封裝在Path類中
| 填充模式 | 介紹 |
|---|---|
| EVEN_ODD | 奇偶規(guī)則 |
| INVERSE_EVEN_ODD | 反奇偶規(guī)則 |
| WINDING | 非零環(huán)繞數(shù)規(guī)則 |
| INVERSE_WINDING | 反非零環(huán)繞數(shù)規(guī)則 |
請(qǐng)記住兩個(gè)填充規(guī)律:
從我之前的文章(1)自定義View基礎(chǔ) - 最易懂的自定義View原理系列提到,圖形是存在方向的(畫圖 = 連接點(diǎn)成的線 = 有連接順序)。

- 具體使用
// 設(shè)置填充規(guī)則
path.setFillType()
// 可填規(guī)則
// 1. EVEN_ODD:奇偶規(guī)則
// 2. INVERSE_EVEN_ODD:反奇偶規(guī)則
// 3. WINDING :非零環(huán)繞數(shù)規(guī)則
// 4. INVERSE_WINDING:反非零環(huán)繞數(shù)規(guī)則
// 理解奇偶規(guī)則和反奇偶規(guī)則:填充效果相反
// 舉例:對(duì)于一個(gè)矩形而言,使用奇偶規(guī)則會(huì)填充矩形內(nèi)部,而使用反奇偶規(guī)則會(huì)填充矩形外部(下面會(huì)舉例說明)
// 獲取當(dāng)前填充規(guī)則
path.getFillType()
// 判斷是否是反向(INVERSE)規(guī)則
path.isInverseFillType()
// 切換填充規(guī)則(即原有規(guī)則與反向規(guī)則之間相互切換)
path.toggleInverseFillType()
實(shí)例1:(奇偶規(guī)則)
// 為了方便觀察,平移坐標(biāo)系
canvas.translate(350, 500);
// 在Path中添加一個(gè)矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
// 設(shè)置Path填充模式為 奇偶規(guī)則
path.setFillType(Path.FillType.EVEN_ODD);
// 反奇偶規(guī)則
// path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
// 畫出路徑
canvas.drawPath(path, mPaint1);

舉例2:(非零環(huán)繞規(guī)則)
// 為了方便觀察,平移坐標(biāo)系
canvas.translate(550, 550);
// 在路徑中添加大正方形
// 逆時(shí)針
path.addRect(-400, -400, 400, 400, Path.Direction.CCW);
// 在路徑中添加小正方形
// 順時(shí)針
// path.addRect(-200, -200, 200, 200, Path.Direction.CW);
// 設(shè)置為逆時(shí)針
path.addRect(-200, -200, 200, 200, Path.Direction.CCW);
// 設(shè)置Path填充模式為非零環(huán)繞規(guī)則
path.setFillType(Path.FillType.WINDING);
// 設(shè)置反非零環(huán)繞數(shù)規(guī)則
// path.setFillType(Path.FillType.INVERSE_WINDING);
// 繪制Path
canvas.drawPath(path, mPaint1);

第五組:布爾操作
- 作用:兩個(gè)路徑Path之間的運(yùn)算
- 應(yīng)用場(chǎng)景:用簡(jiǎn)單的圖形通過特定規(guī)則合成相對(duì)復(fù)雜的圖形。
- 具體使用
// 方法1
boolean op (Path path, Path.Op op)
// 舉例
// 對(duì) path1 和 path2 執(zhí)行布爾運(yùn)算,運(yùn)算方式由第二個(gè)參數(shù)指定
// 運(yùn)算結(jié)果存入到path1中。
path1.op(path2, Path.Op.DIFFERENCE);
// 方法2
boolean op (Path path1, Path path2, Path.Op op)
// 舉例
// 對(duì) path1 和 path2 執(zhí)行布爾運(yùn)算,運(yùn)算方式由第三個(gè)參數(shù)指定
// 運(yùn)算結(jié)果存入到path3中。
path3.op(path1, path2, Path.Op.DIFFERENCE)
之間的運(yùn)算方式(即Path.Op參數(shù))如下

舉例:
// 為了方便觀察,平移坐標(biāo)系
canvas.translate(550, 550);
// 畫兩個(gè)圓
// 圓1:圓心 = (0,0),半徑 = 100
// 圓2:圓心 = (50,0),半徑 = 100
path1.addCircle(0, 0, 100, Path.Direction.CW);
path2.addCircle(50, 0,100, Path.Direction.CW);
// 取兩個(gè)路徑的異或集
path1.op(path2, Path.Op.XOR);
// 畫出路徑
canvas.drawPath(path1, mPaint1);

4. 貝賽爾曲線
- 定義:計(jì)算曲線的數(shù)學(xué)公式
- 作用:計(jì)算并表示曲線
任何一條曲線都可以用貝塞爾曲線表示
- 具體使用:貝塞爾曲線可通過1數(shù)據(jù)點(diǎn)和若干個(gè)控制點(diǎn)描述
- 數(shù)據(jù)點(diǎn):指路徑的起始點(diǎn)和終止點(diǎn);
- 控制點(diǎn):決定了路徑的彎曲軌跡;
- n+1階貝塞爾曲線 = 有n個(gè)控制點(diǎn);
- (1階 = 一條直線,高階可以拆解為多條低階曲線)
Canvas提供了畫二階 & 三階貝塞爾曲線的方法,下面是具體方法:
// 繪制二階貝塞爾曲線
// (x1,y1)為控制點(diǎn),(x2,y2)為終點(diǎn)
quadTo(float x1, float y1, float x2, float y2)
// (x1,y1)為控制點(diǎn)距離起點(diǎn)的偏移量,(x2,y2)為終點(diǎn)距離起點(diǎn)的偏移量
rQuadTo(float x1, float y1, float x2, float y2)
// 繪制三階貝塞爾曲線
// (x1,y1),(x2,y2)為控制點(diǎn),(x3,y3)為終點(diǎn)
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
// (x1,y1),(x2,y2)為控制點(diǎn)距離起點(diǎn)的偏移量,(x3,y3)為終點(diǎn)距離起點(diǎn)的偏移量
rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
此處只簡(jiǎn)單介紹貝塞爾曲線,想詳細(xì)理解可以參考這篇文章。
5. 總結(jié)
通過閱讀本文,相信你已經(jīng)全面了解Path類的使用。Carson帶你學(xué)Android自定義View文章系列:
Carson帶你學(xué)Android:自定義View基礎(chǔ)
Carson帶你學(xué)Android:一文梳理自定義View工作流程
Carson帶你學(xué)Android:自定義View繪制準(zhǔn)備-DecorView創(chuàng)建
Carson帶你學(xué)Android:自定義View Measure過程
Carson帶你學(xué)Android:自定義View Layout過程
Carson帶你學(xué)Android:自定義View Draw過程
Carson帶你學(xué)Android:手把手教你寫一個(gè)完整的自定義View
Carson帶你學(xué)Android:Canvas類全面解析
Carson帶你學(xué)Android:Path類全面解析
歡迎關(guān)注Carson_Ho的簡(jiǎn)書
不定期分享關(guān)于安卓開發(fā)的干貨,追求短、平、快,但卻不缺深度。



