Path從懵逼到精通——基本操作

Deziko發表於2017-03-19

什麼是Path?

我們先看看Android官方文件給出的定義:

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.

這裡大概翻譯就是:
Path類封裝了直線段,二次貝塞爾曲線和三次貝塞爾曲線的幾何路徑。
可以使用Canvas中drawPath方法將Path畫出來。Path不僅可以使用Paint的填充模式和描邊模式,也可以用畫布裁剪和或者畫文字。

總而言之,Path就是可以畫出通過直線或者曲線的各種組合就可以做出很多很牛X的效果。

至於Path能做出多牛X的效果?上圖給你們看看:

Path從懵逼到精通——基本操作

Path從懵逼到精通——基本操作

Path從懵逼到精通——基本操作

Path從懵逼到精通——基本操作

Path從懵逼到精通——基本操作

怎麼使用Path?

要想用Path做出牛X的效果之前,就需要熟悉它的基本操作,這篇文章主要介紹的是Path的一些基本API,進階的用法將會放在下一篇文章。

以下是Path的基本操作的方法:

第一類(直線與點的操作):lineTo,moveTo,setLastPoint,close

第二類(基本形狀):

addXxx,arcTo

第三類(設定方法) :

set(),offset(),reset()

第四類(判斷方法) :isConvex(),isEmpty(),isRect(RectF rect)

在說這些方法之前都要做一個畫筆的初始化,程式碼如下:

private void initPaint() {
  mPaint = new Paint();       // 建立畫筆
  mPaint.setColor(Color.BLACK);  // 畫筆顏色 - 黑色
  mPaint.setStyle(Paint.Style.STROKE);  // 填充模式 - 描邊
  mPaint.setStrokeWidth(10);  
}複製程式碼

第一類(直線與點的操作):

1.1 lineTo:

方法預覽:
public void lineTo (float x, float y)複製程式碼
有什麼用:

顧名思義,這個方法就是畫一條直線的。確定一條直線需要兩個點,但是這個方法裡只提供了一個點的座標啊?那另一個點的座標是什麼呢?這個點其實就是Path物件上次呼叫的最後一個點的座標,如果在呼叫lineTo()方法前,並沒有呼叫過任何Path的操作,那這個點就預設為座標原點。

怎麼用:

畫出直線:

    Path path = new Path(); //建立Path物件
    path.lineTo(300, 300); //建立一條從原點到座標(300,300)的直線
    canvas.drawPath(path, mPaint);//畫出路徑複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.lineTo(300, 300);

這個時候我在path.lintTo(300,300),後面再加一句 path.lineTo(100, 200); 看看效果如何?

    Path path = new Path(); //建立Path物件
    path.lineTo(300, 300); //建立一條從原點到座標(300,300)的直線
    path.lineTo(100, 200); //建立從(300,300)到(100,200)的一條直線 
    canvas.drawPath(path, mPaint);//畫出路徑複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.lineTo(100, 200);

可以看到第二段線段的是從(300,300)到(100,200)的,那就可以知道lineTo方法的連線的起點是由lineTo方法上一個Path操作決定的。

1.2 moveTo:

方法預覽:
public void moveTo(float x, float y)複製程式碼
有什麼用:

這個方法的作用就是將下次畫路徑起點移動到(x,y)

怎麼用:

還是用上面的程式碼:

    Path path = new Path(); //建立Path物件
    path.lineTo(300, 300); //建立一條從原點到座標(300,300)的直線
    path.moveTo(0,0);  //將下一次操作路徑的起點座標移到(0,0)
    path.lineTo(100, 200); //建立從(0,0)到(100,200)的一條直線 
    canvas.drawPath(path, mPaint);//畫出路徑複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.moveTo(0,0);

可以看到在path.lineTo(100, 200);之前呼叫了path.moveTo(0, 0);方法,那就將lineTo的操作起始點移動到(0,0)。

1.3 setLastPoint:

方法預覽:
public void setLastPoint(float dx, float dy)複製程式碼
有什麼用:

改變上一次操作路徑的結束座標點

怎麼用:
    Path path = new Path(); //建立Path物件
    path.lineTo(300, 300); //建立一條從原點到座標(300,300)的直線
    path.setLastPoint(500,500);  //將上一次的操作路徑的終點移動到(500,500)
    path.lineTo(100, 200); //建立從(500,500)到(100,200)的一條直線 
    canvas.drawPath(path, mPaint);//畫出路徑複製程式碼
效果如下:

Path從懵逼到精通——基本操作
path.setLastPoint(500,500);

可以知道在執行lineTo(100, 100)時座標點是(100,100),使用setLastPoint(500, 500)後就變成(500,500),並且也會影響上一次操作路徑的終點。

在這裡我們就可以總結:

方法 作用
moveTo 會影響下次操作,不會影響上一次操作
setLastPoint 會影響下次操作,也會影響上一次操作

1.4 close:

方法預覽:
public void close()複製程式碼
有什麼用:

封閉當前路徑,如果當前的點不等於路徑的起始點,就會在整個操作的最後的點與起始點之間新增線段。

######怎麼用:

    Path path = new Path(); //建立Path物件
    path.lineTo(300, 300); //建立一條從原點到座標(300,300)的直線
    path.lineTo(100, 200); //建立從(100,200)到(100,200)的一條直線 
    path.close(); //封閉路徑
    canvas.drawPath(path, mPaint);//畫出路徑複製程式碼
效果如下:

Path從懵逼到精通——基本操作
path.close();

可以看到在執行close方法之後,在(100,200)與(0,0)之間新增了一條直線

第二類(基本形狀):

2.1 addXxx,arcTo

方法預覽:

//矩形             
public void addRect(RectF rect, Direction dir)

public void addRect(float left, float top, float right, float bottom, Direction dir)

//圓形
public void addCircle(float x, float y, float radius, Direction dir)


//圓角矩形
public void addRoundRect(RectF rect, float[] radii, Direction dir)

public void addRoundRect (float left,float top,float right,float bottom,float rx,float ry,Path.Direction dir)

public void addRoundRect (RectF rect,float[] radii,Path.Direction dir)

public void addRoundRect (float left,float top,float right,float bottom,float[] radii,Path.Direction dir)

//橢圓
public void addOval(RectF oval, Direction dir)

public void addOval (float left,float top,float right,float bottom,Path.Direction dir)

//圓弧
public void addArc (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)

// 新增Path
public void addPath (Path src)
public void addPath (Path src, float dx, float dy)
public void addPath (Path src, Matrix matrix)複製程式碼
2.1.1 addRect(矩形):
方法預覽:
public void addRect(RectF rect, Direction dir)

public void addRect(float left, float top, float right, float bottom, Direction dir)複製程式碼
有什麼用:

畫出一個矩形

怎麼用:
Path path = new Path();  //建立Path物件
RectF rect = new RectF(0, 0, 400, 400);
path.addRect(rect,Path.Direction.CW);
//path.addRect(0, 0, 400, 400, Path.Direction.CW);
//這個方法與上一句是同樣的效果
canvas.drawPath(path, mPaint);複製程式碼
效果圖:

Path從懵逼到精通——基本操作
path.addRect

解釋:

addRect兩個方法當中前面的所有引數其實都是確定一個矩形,這裡就不說矩形的原理了,現在重點來說一下addRect的最後一個引數:Path.Direction dir。

這個引數是什麼意思呢?這個引數就是確定當畫這個矩形的時候究竟是順時針方向畫呢?還是用逆時針方向畫。

Path.Direction.CW代表順時針,Path.Direction.CCW代表逆時針。那這個方法究竟從哪個點開始畫呢?我們來驗證一下

Path path = new Path();
path.addRect(0, 0, 400, 400, Path.Direction.CW);
path.setLastPoint(0, 300);
canvas.drawPath(path, mPaint);複製程式碼

我們在addRect之後增加setLastPoint方法,重新設定最後一個點的座標。

效果如下:

Path從懵逼到精通——基本操作
path.addRect(0, 0, 400, 400, Path.Direction.CW);

如果這個時候我們將矩形的方向換成逆時針方向,看看效果如何:

Path path = new Path();
path.addRect(0, 0, 400, 400, Path.Direction.CCW);
path.setLastPoint(300,0);
canvas.drawPath(path, mPaint);複製程式碼
效果如下:

Path從懵逼到精通——基本操作
path.addRect(0, 0, 400, 400, Path.Direction.CCW);

從以上兩個效果就知道,addRect方向是從左上上角開始算起的。所以順時針和逆時針的方向是會影響到繪製效果的。

2.1.2 addCircle(圓形):
方法預覽:

public void addCircle(float x, float y, float radius, Direction dir)

有什麼用:

畫出一個圓形

怎麼用:
    Path path = new Path(); 
    path.addCircle(200, 200, 100, Direction.CW); //建立一個圓心座標為(200,200),半徑為100的圓
    canvas.drawPath(path, mPaint);複製程式碼
效果如下:

Path從懵逼到精通——基本操作
path.addCircle(200, 200, 100, Direction.CW);

2.1.3 addRoundRect(圓角矩形):
方法預覽:
public void addRoundRect(RectF rect, float rx, float ry, Direction dir)

public void addRoundRect (float left,float top,float right,float bottom,float rx,float ry,Path.Direction dir)

public void addRoundRect (RectF rect,float[] radii,Path.Direction dir)

public void addRoundRect (float left,float top,float right,float bottom,float[] radii,Path.Direction dir)複製程式碼
有什麼用:

畫出一個圓角矩形

怎麼用:

在說這個方法怎麼用之前,要先說一下圓角矩形的構成原理。
請看下面這幅圖:

Path從懵逼到精通——基本操作
圓角矩形.jpg

圓角矩形的圓角其實就是一段圓弧,圓弧需要什麼才能確定它的位置和大小呢?答案就是圓心和半徑,那為什麼上面的方法會出現兩個半徑呢?其實這個並不是正圓的半徑,而是橢圓的半徑。

    Path path = new Path();
    RectF rect = new RectF(100,100,800,500);
    path.addRoundRect(rect, 150, 100, Direction.CW); //建立一個圓角矩形
    canvas.drawPath(path, mPaint);複製程式碼
效果如下:

Path從懵逼到精通——基本操作
path.addRoundRect

現在我們看一下,圓角矩形後面的那兩個方法,這兩個方法都有一個引數: float[] radii 。這個引數的意思就是控制圓角的四個角的半徑。
這個陣列至少要有8個值,如果少於8個值就會報異常。這8個值分成4組,每組的第一和第二個值分別代表圓角的x半徑和y半徑。
每組資料也會作用於圓角矩形的不同位置,總結如下

值的位置 作用圓角矩形哪個角
0,1 左上角
2,3 右上角
4,5 右下角
6,7 左下角
2.1.4 addOval(橢圓):
方法預覽:
//橢圓
public void addOval(RectF oval, Direction dir)

public void addOval (float left,float top,float right,float bottom,Path.Direction dir)複製程式碼
有什麼用:

畫一個橢圓

怎麼用:

為了便於觀察,我將橢圓中的引數的矩形用不同顏色畫出來。

  Path path = new Path();
  RectF rect = new RectF(100,100,800,500);
  mPaint.setColor(Color.GRAY);
  mPaint.setStyle(Style.FILL);
  canvas.drawRect(rect, mPaint);
  mPaint.setColor(Color.BLACK);
  path.addOval(rect, Direction.CW);
  canvas.drawPath(path, mPaint);複製程式碼
效果如下:

Path從懵逼到精通——基本操作
path.addOval(rect, Direction.CW);

從效果圖就可以知道,這個就是矩形的內切圓。那如果想用這個方法畫出正圓應該怎麼畫呢?沒錯,就是將這個矩形變成正方形,畫出來的圓就是正圓了。現在驗證一下:

  Path path = new Path();
  RectF rect = new RectF(100,100,800,800); //將矩形變成正方形
  mPaint.setColor(Color.GRAY);
  mPaint.setStyle(Style.FILL);
  canvas.drawRect(rect, mPaint);
  mPaint.setColor(Color.BLACK);
  path.addOval(rect, Direction.CW);
  canvas.drawPath(path, mPaint);複製程式碼
效果如下:

Path從懵逼到精通——基本操作
用addOval畫出正圓

2.1.5 addArc與arcTo(圓弧):
方法預覽:
//圓弧
public void addArc (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)複製程式碼

先說一下 startAngle sweepAngle 這兩個引數的意思。

引數 意思
startAngle 開始的角度
sweepAngle 掃過的角度

startAngle 是代表開始的角度,那麼Android中矩形的0°是從哪裡開始呢?其實矩形的0°是在矩形的右邊的中點,按順時針方向逐漸增大。

如圖:

Path從懵逼到精通——基本操作
開始角度

sweepAngle 掃過的角度就是從起點角度開始掃過的角度,並不是指終點的角度。例如如果你的startAngle是90°,sweepAngle是180°。那麼這個圓弧的終點應該在270°,而不是在180°。

現在驗證一下看看:

  Path path = new Path();
  RectF rect = new RectF(300,300,1000,800);
  mPaint.setColor(Color.GRAY);
  mPaint.setStyle(Style.FILL);
  canvas.drawRect(rect, mPaint);
  mPaint.setColor(Color.BLACK);
  path.addArc(rect, 90, 180);
  canvas.drawPath(path, mPaint);複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.addArc(rect, 90, 180);

知道了addArc的用法之後,我們來看一下arcTo這個方法,這個方法也是用來畫圓弧的,但是與addArc有些不同,總結如下:

方法 作用
addArc 直接新增一段圓弧
arcTo 新增一段圓弧,如果圓弧的起點與上一次Path操作的終點不一樣的話,就會在這兩個點連成一條直線

舉個例子:

    Path path = new Path();
    RectF rect = new RectF(300,300,1000,800);
    mPaint.setColor(Color.GRAY);
    mPaint.setStyle(Style.FILL);
    canvas.drawRect(rect, mPaint);
    mPaint.setColor(Color.BLACK);
    mPaint.setStyle(Style.STROKE);
    path.lineTo(100, 100); //用path畫一條從(0,0)到(100,100)的直線
    path.arcTo(rect, 90, 180); //用arcTo方法畫一段圓弧
    canvas.drawPath(path, mPaint); //直線終點(100,100)與圓弧起點會連成一條直線複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.arcTo

如果你不想這兩個點連線的話,arcTo在一個方法中有forceMoveTo的引數,這個引數如果設為true就說明將上一次操作的點設為圓弧的起點,也就是說不會將圓弧的起點與上一次操作的點連線起來。如果設為false就會連線。

來驗證一下:

    Path path = new Path();
    RectF rect = new RectF(300,300,1000,800);
    mPaint.setColor(Color.GRAY);
    mPaint.setStyle(Style.FILL);
    canvas.drawRect(rect, mPaint);
    mPaint.setColor(Color.BLACK);
    mPaint.setStyle(Style.STROKE);
    path.lineTo(100, 100); //用path畫一條從(0,0)到(100,100)的直線
    path.arcTo(rect, 90, 180,true); //用arcTo方法畫一段圓弧
    canvas.drawPath(path, mPaint); //直線終點(100,100)與圓弧起點不會連成一條直線複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.arcTo(rect, 90, 180,true);

2.1.6 addPath(新增Path):
方法預覽:
    //新增Path:
    public void addPath (Path src)
    public void addPath (Path src, float dx, float dy)
    public void addPath (Path src, Matrix matrix)複製程式碼

######有什麼用:
將兩個Path合併在一起

######怎麼用:
這裡先講addPath的前兩個方法,最後那個方法等寫到Matrix才細講。

  Path path = new Path();
  Path src = new Path();
  path.addRect(0, 0, 400, 400, Path.Direction.CW); //寬高為400的矩形
  src.addCircle(200, 200, 100, Path.Direction.CW); //圓心為(200,200)半徑為100的正圓
  path.addPath(src);
  canvas.drawPath(path, mPaint);複製程式碼

######效果如下:

Path從懵逼到精通——基本操作
path.addPath(src);

addPath的第二個方法的 dx dy 兩個引數是什麼意思呢?
其實它們是代表新增path後的位移值。
例如,上面這個例子,如果我將path.addPath(src);改成path.addPath(src,200,0);會出現什麼現象呢?這時候src畫的圓的圓心的座標會移動到(400,200)。

讓我們來驗證一下:

  Path path = new Path();
  Path src = new Path();
  path.addRect(0, 0, 400, 400, Path.Direction.CW); //寬高為400的矩形
  src.addCircle(200, 200, 100, Path.Direction.CW); //圓心為(200,200)半徑為100的正圓
  //path.addPath(src);
  path.addPath(src,200,0);
  canvas.drawPath(path, mPaint);複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.addPath(src,200,0);

path畫出寬高為400的矩形,src畫出一個圓心為(0,0),半徑為100的圓。path.addPath將src合併到一起,並將src的中心設定為(200,200)。

第三類(設定方法):

3.1 set()

方法預覽:
 public void set(Path src)複製程式碼
有什麼用:

將新的path賦值到現有的path

怎麼用:
  Path path = new Path();
  Path src = new Path();
  path.addRect(0, 0, 400, 400, Path.Direction.CW);
  src.addCircle(200, 200, 100, Path.Direction.CW);
  path.set(src); // 相當於 path = src;
  canvas.drawPath(path, mPaint);複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.set(src);

這個方法就是將path之前的矩形變成圓形。

3.2 offset()

方法預覽:
 public void offset (float dx, float dy)
 public void offset (float dx, float dy, Path dst)複製程式碼
有什麼用:

將path進行平移

怎麼用:
  Path path = new Path();
  path.addRect(0, 0, 400, 400, Path.Direction.CW);
  canvas.drawPath(path, mPaint);
  mPaint.setColor(Color.RED); //將畫筆變成紅色
  path.offset(100,0);  //將path向右平移
  canvas.drawPath(path, mPaint);複製程式碼
效果如下:

Path從懵逼到精通——基本操作
path.offset(100,0);

offset的第二個方法的第三個引數的意思就是將平移後的path儲存到dst引數中。
如果傳入dst不為空,將平移後的狀態儲存到dst中,不影響當前path。dst為空,平移作用當前的path,相當於第一個方法。
現在驗證一下:

  Path path = new Path();
  Path dst = new Path();
  path.addRect(0, 0, 400, 400, Direction.CW); //path新增矩形
  dst.addCircle(100,100, 100, Direction.CW); //dst新增圓形
  path.offset(100,0,dst); //將平移後的path儲存到dst
  canvas.drawPath(dst, mPaint);複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.offset(100,0,dst);

3.3 reset()

######方法預覽:

public void reset()複製程式碼
有什麼用:

這個方法很簡單,就是將path的所有操作都清空掉。

第四類(判斷方法) :

4.1 isConvex()(這個方法在API21之後才有)

方法預覽:
public boolean isConvex ()複製程式碼
有什麼用:

判斷path是否為凸多邊形,如果是就為true,反之為false。

要理解這個方法首先,我們要知道什麼是凸多邊形。
凸多邊形的概念:
1.每個內角小於180度
2.任何兩個頂點間的線段位於多邊形的內部或邊界上。

也就是說矩形,三角形,直線都是凸多邊形,但是五角星那種形狀就不是。現在我們用程式碼驗證一下:

程式碼如下:

        Path path = new Path();
        path.moveTo(600,600);
        path.lineTo(500,700);
        path.lineTo(380,700);
        path.lineTo(500,780);
        path.close();
        Log.e("Path", "===============path.isConvex() " + path.isConvex());
        canvas.drawPath(path,mPaint);複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.isConvex()

列印的結果為:

E Path    : ===============path.isConvex() false複製程式碼

因為該圖形並不是凸多邊形,所以返回false。

但這裡有個坑,如果我直接使用addRect方法,然後用setLastPoint來將這個矩形變成凹多邊形。

程式碼如下:

  Path path = new Path();
  RectF rect = new RectF(0,0,400,400);
  path.addRect(rect, Direction.CCW);
  path.setLastPoint(100, 300);
  Log.e("Path", "===============path.isConvex() " + path.isConvex());
  canvas.drawPath(path,mPaint);複製程式碼

效果如下:

Path從懵逼到精通——基本操作
path.isConvex()

可以看出圖形是一個凹多邊形,但是列印的資訊卻是:

E Path    : ===============path.isConvex() true複製程式碼

這個地方我一直也想不明白,為什麼會返回true。等我以後有思路了再來解決這個問題吧。

4.2 isEmpty:

######方法預覽:

  public boolean isEmpty ()複製程式碼

######有什麼用:
判斷path中是否包含內容:

怎麼用:
  Path path = new Path();
  Log.e("path.isEmpty()","==============path.isEmpty()1: " + path.isEmpty());

  path.lineTo(100,100);
  Log.e("path.isEmpty()","==============path.isEmpty()2: " + path.isEmpty());複製程式碼

Log輸出的結果:

E path.isEmpty(): ==============path.isEmpty()1: true
E path.isEmpty(): ==============path.isEmpty()2: false複製程式碼

###4.3 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輸出的結果:

E Rect    : ======isRect:true| left:0.0| top:0.0| right:400.0| bottom:400.0複製程式碼

####參考資料:

相關文章