Android自定義控件學(xué)習(xí)筆記(四)

自定義控件系列的讀書筆記偶妖,整理自下列資料奶赠,不代表博主個人觀點 :GcsSloop/AndroidNote


六捎迫、Path基本操作

6.1 Path常用方法表

不包括 API21以上才添加的方法

作用 相關(guān)方法 備注
移動起點 moveTo 移動下一次操作的起點位置
設(shè)置終點 setLastPoint 重置當(dāng)前path中最后一個點位置武鲁,如果在繪制之前調(diào)用灭翔,效果和moveTo相同
連接直線 lineTo 添加上一個點到當(dāng)前點之間的直線到Path
閉合路徑 close 連接第一個點連接到最后一個點嵌言,形成一個閉合區(qū)域
添加內(nèi)容 addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 添加(矩形, 圓角矩形及穗, 橢圓摧茴, 圓, 路徑埂陆, 圓弧) 到當(dāng)前Path (注意addArc和arcTo的區(qū)別)
是否為空 isEmpty 判斷Path是否為空
是否為矩形 isRect 判斷path是否是一個矩形
替換路徑 set 用新的路徑替換到當(dāng)前路徑所有內(nèi)容
偏移路徑 offset 對當(dāng)前路徑之前的操作進行偏移(不會影響之后的操作)
貝塞爾曲線 quadTo, cubicTo 分別為二次和三次貝塞爾曲線的方法
rXxx方法 rMoveTo, rLineTo, rQuadTo, rCubicTo 不帶r的方法是基于原點的坐標(biāo)系苛白, rXxx方法是基于當(dāng)前點坐標(biāo)系
填充模式 setFillType, getFillType, isInverseFillType, toggleInverseFillType 設(shè)置,獲取,判斷和切換填充模式
提示方法 incReserve 提示Path還有多少個點等待加入(這個方法貌似會讓Path優(yōu)化存儲結(jié)構(gòu))
布爾操作(API19) op 對兩個Path進行布爾運算(即取交集、并集等操作)
計算邊界 computeBounds 計算Path的邊界
重置路徑 reset, rewind 清除Path中的內(nèi)容焚虱,reset不保留內(nèi)部數(shù)據(jù)結(jié)構(gòu)购裙,但會保留FillType;rewind會保留內(nèi)部的數(shù)據(jù)結(jié)構(gòu)鹃栽,但不保留FillType
矩陣操作 transform 矩陣變換

6.2 Path詳解

請關(guān)閉硬件加速躏率,以免引起不必要的問題!

6.2.1 Path作用

Path在2D繪圖中是一個很重要的東西民鼓,使用Path不僅能夠繪制簡單圖形薇芝,也可以繪制這些比較復(fù)雜的圖形。另外丰嘉,根據(jù)路徑繪制文本和剪裁畫布都會用到Path夯到。

6.2.2 Path含義

Path封裝了由直線和曲線(二次,三次貝塞爾曲線)構(gòu)成的幾何路徑饮亏。你能用Canvas中的drawPath來把這條路徑畫出來(同樣支持Paint的不同繪制模式)耍贾,也可以用于剪裁畫布和根據(jù)路徑繪制文字。我們有時會用Path來描述一個圖像的輪廓路幸,所以也會稱為輪廓線(輪廓線僅是Path的一種使用方法荐开,兩者并不等價)

另外路徑有開放和封閉的區(qū)別。

圖像 名稱 備注
封閉路徑 首尾相接形成了一個封閉區(qū)域
開放路徑 沒有首位相接形成封閉區(qū)域

6.2.3 Path使用方法詳解

(1)第1組:moveTo劝赔、 setLastPoint誓焦、 lineTo 和 close

先創(chuàng)建一個通用的畫筆:

Paint mPaint = new Paint();             // 創(chuàng)建畫筆
mPaint.setColor(Color.BLACK);           // 畫筆顏色 - 黑色
mPaint.setStyle(Paint.Style.STROKE);    // 填充模式 - 描邊
mPaint.setStrokeWidth(10);              // 邊框?qū)挾?- 10

lineTo:

public void lineTo (float x, float y)

lineTo是指從某個點到參數(shù)坐標(biāo)點之間連一條線,這里的某個點就是上次操作結(jié)束的點着帽,如果沒有進行過操作則默認點為坐標(biāo)原點:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心(寬高數(shù)據(jù)在onSizeChanged中獲取)
Path path = new Path();                     // 創(chuàng)建Path
path.lineTo(200, 200);                      // lineTo
path.lineTo(200,0);
canvas.drawPath(path, mPaint);              // 繪制Path

在示例中我們調(diào)用了兩次lineTo杂伟,第一次由于之前沒有過操作,所以默認點就是坐標(biāo)原點O仍翰,結(jié)果就是坐標(biāo)原點O到A(200,200)之間連直線(用藍色圈1標(biāo)注)赫粥。

第二次lineTo的時候,由于上次的結(jié)束位置是A(200,200),所以就是A(200,200)到B(200,0)之間的連線(用藍色圈2標(biāo)注)予借。

moveTo 和 setLastPoint:

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

// setLastPoint
public void setLastPoint (float dx, float dy)
方法名 簡介 是否影響之前的操作 是否影響之后操作
moveTo 移動下一次操作的起點位置
setLastPoint 設(shè)置之前操作的最后一個點位置

moveTo示例代碼:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心
Path path = new Path();                     // 創(chuàng)建Path
path.lineTo(200, 200);                      // lineTo
path.moveTo(200,100);                       // moveTo
path.lineTo(200,0);                         // lineTo
canvas.drawPath(path, mPaint);              // 繪制Path

moveTo只改變下次操作的起點越平,在執(zhí)行完第一次LineTo的時候频蛔,本來的默認點位置是A(200,200),但是moveTo將其改變成為了C(200,100),所以在第二次調(diào)用lineTo的時候就是連接C(200,100) 到 B(200,0) 之間的直線(用藍色圈2標(biāo)注)。

下面是setLastPoint的示例:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心
Path path = new Path();                     // 創(chuàng)建Path
path.lineTo(200, 200);                      // lineTo
path.setLastPoint(200,100);                 // setLastPoint
path.lineTo(200,0);                         // lineTo
canvas.drawPath(path, mPaint);              // 繪制Path

setLastPoint是重置上一次操作的最后一個點秦叛,在執(zhí)行完第一次的lineTo的時候晦溪,最后一個點是A(200,200),而setLastPoint更改最后一個點為C(200,100)挣跋,所以在實際執(zhí)行的時候三圆,第一次的lineTo就不是從原點O到A(200,200)的連線了避咆,而變成了從原點O到C(200,100)之間的連線了舟肉。

在執(zhí)行完第一次lineTo和setLastPoint后,最后一個點的位置是C(200,100),所以在第二次調(diào)用lineTo的時候就是C(200,100) 到 B(200,0) 之間的連線(用藍色圈2標(biāo)注)查库。

close:

public void close ()

close方法用于連接當(dāng)前最后一個點和最初的一個點(如果兩個點不重合的話)路媚,最終形成一個封閉的圖形。

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心
Path path = new Path();                     // 創(chuàng)建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的作用是封閉路徑院领,與連接當(dāng)前最后一個點和第一個點并不等價。如果連接了最后一個點和第一個點仍然無法形成封閉圖形够吩,則close什么也不做比然。

(2)第2組:addXxx與arcTo

這次內(nèi)容主要是在Path中添加基本圖形,重點區(qū)分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中添加一個基本形狀强法,基本形狀部分和前面所講的繪制基本形狀并無太大差別。

仔細觀察一下第一類的方法湾笛,無一例外饮怯,在最后都有一個Path.Direction。Direction的意思是方向嚎研,趨勢蓖墅,是一個枚舉(Enum)類型,里面只有兩個枚舉常量临扮,如下:

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

它們的作用如下有:一是在添加圖形時確定閉合順序(各個點的記錄順序)论矾;二是對圖形的渲染結(jié)果有影響(是判斷圖形渲染的重要條件) 。

先研究確定閉合順序的問題杆勇,添加一個矩形:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
canvas.drawPath(path,mPaint);

將上面代碼的CW改為CCW再運行一次贪壳,會發(fā)現(xiàn)兩次運行結(jié)果一模一樣!

想要讓它現(xiàn)出原形蚜退,就要用到剛剛學(xué)到的setLastPoint(重置當(dāng)前最后一個點的位置)闰靴。

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
path.setLastPoint(-300,300);                // <-- 重置最后一個點的位置
canvas.drawPath(path,mPaint);

Path是使用四個點來記錄矩形彪笼,對于上面這個矩形來說,采用的是順時針(CW)蚂且,所以記錄的點的順序是 A -> B -> C -> D. 最后一個點就是D配猫,我們這里使用setLastPoint改變最后一個點的位置實際上是改變了D的位置。

理解了上面的原理之后杏死,假設(shè)我們將順時針改為逆時針(CCW)章姓,則記錄點的順序應(yīng)該就是 A - D -> C -> B, 再使用setLastPoint則改變的是B的位置,如下:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CCW);
path.setLastPoint(-300,300);                // <-- 重置最后一個點的位置
canvas.drawPath(path,mPaint);

參數(shù)中點的順序很重要识埋!

第二類(Path)

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

這個相對比較簡單,也很容易理解零渐,就是將兩個Path合并成為一個窒舟。

第三個方法是將src添加到當(dāng)前path之前先使用Matrix進行變換。

第二個方法比第一個方法多出來的兩個參數(shù)是將src進行了位移之后再添加進當(dāng)前path中诵盼。

示例:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心
canvas.scale(1,-1);                         // <-- 注意 翻轉(zhuǎn)y坐標(biāo)軸
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(矩形和圓形)中心都是坐標(biāo)原點惠豺,我們在將包含圓形的path添加到包含矩形的path之前將其進行移動了一段距離,最終繪制出來的效果就如上面所示风宁。

第三類(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)

從名字就可以看出洁墙,這兩個方法都是與圓弧相關(guān)的,作用都是添加一個圓弧到path中戒财,但既然存在兩個方法热监,兩者之間肯定是有區(qū)別的:

名稱 作用 區(qū)別
addArc 添加一個圓弧到path 直接添加一個圓弧到path中
arcTo 添加一個圓弧到path 添加一個圓弧到path,如果圓弧的起點和上次最后一個坐標(biāo)點不相同饮寞,就連接兩個點

可以看到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);  // 移動坐標(biāo)系到屏幕中心
canvas.scale(1,-1);                         // <-- 注意 翻轉(zhuǎn)y坐標(biāo)軸

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);  // 移動坐標(biāo)系到屏幕中心
canvas.scale(1,-1);                         // <-- 注意 翻轉(zhuǎn)y坐標(biāo)軸

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)第3組:isEmpty、 isRect柒爵、isConvex臼勉、 set 和 offset

isEmpty:

public boolean isEmpty ()

判斷path中是否包含內(nèi)容。

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

log輸出結(jié)果:

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是否是一個矩形餐弱,如果是一個矩形的話宴霸,會將矩形的信息存放進參數(shù)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 輸出結(jié)果:

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賦值到現(xiàn)有path。

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心
canvas.scale(1,-1);                         // <-- 注意 翻轉(zhuǎn)y坐標(biāo)軸

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);                              // 大致相當(dāng)于 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只作用于當(dāng)前path氓扛。

第二個方法中最后的參數(shù)dst是存儲平移后的path的枯芬。

dst狀態(tài) 效果
dst不為空 將當(dāng)前path平移后的狀態(tài)存入dst中,不會影響當(dāng)前path
dst為空(null) 平移將作用于當(dāng)前path采郎,相當(dāng)于第一種方法

示例:

canvas.translate(mWidth / 2, mHeight / 2);  // 移動坐標(biāo)系到屏幕中心
canvas.scale(1,-1);                         // <-- 注意 翻轉(zhuǎn)y坐標(biāo)軸

Path path = new Path();                     // path中添加一個圓形(圓心在坐標(biāo)原點)
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中添加了一個矩形,但是并沒有表現(xiàn)出來蒜埋,所以淫痰,當(dāng)dst中存在內(nèi)容時,dst中原有的內(nèi)容會被清空整份,而存放平移后的path待错。

(4)rXxx方法

此類方法可以看到和前面的方法看起來很像,只是在前面多了一個r烈评,r代表的是相對坐標(biāo)火俄。rXxx方法的坐標(biāo)使用的是相對位置(基于當(dāng)前點的位移),而之前方法的坐標(biāo)是絕對位置(基于當(dāng)前坐標(biāo)系的坐標(biāo))讲冠。

舉個例子:

Path path = new Path();
path.moveTo(100,100);
path.lineTo(100,200);
canvas.drawPath(path,mDeafultPaint);

改成r方法后:

Path path = new Path();
path.moveTo(100,100);
path.rLineTo(100,200);
canvas.drawPath(path,mDeafultPaint);

6.3 PathMeasure

常用方法如下:

返回值 方法名 釋義
void setPath(Path path, boolean forceClosed) 關(guān)聯(lián)一個Path
boolean isClosed() 是否閉合
float getLength() 獲取Path的長度
boolean nextContour() 跳轉(zhuǎn)到下一個輪廓
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 截取片段
boolean getPosTan(float distance, float[] pos, float[] tan) 獲取指定長度的位置坐標(biāo)及該點切線值
boolean getMatrix(float distance, Matrix matrix, int flags) 獲取指定長度的位置坐標(biāo)及該點Matrix

6.3.1 構(gòu)造函數(shù)

(1)無參構(gòu)造函數(shù)
PathMeasure ()

用這個構(gòu)造函數(shù)可創(chuàng)建一個空的 PathMeasure瓜客,但是使用之前需要先調(diào)用 setPath 方法來與 Path 進行關(guān)聯(lián)。被關(guān)聯(lián)的 Path 必須是已經(jīng)創(chuàng)建好的竿开,如果關(guān)聯(lián)之后 Path 內(nèi)容進行了更改忆家,則需要使用 setPath 方法重新關(guān)聯(lián)

(2)有參構(gòu)造函數(shù)
PathMeasure (Path path, boolean forceClosed)

用這個構(gòu)造函數(shù)是創(chuàng)建一個 PathMeasure 并關(guān)聯(lián)一個 Path德迹, 其實和創(chuàng)建一個空的 PathMeasure 后調(diào)用 setPath 進行關(guān)聯(lián)效果是一樣的芽卿,同樣,被關(guān)聯(lián)的 Path 也必須是已經(jīng)創(chuàng)建好的胳搞,如果關(guān)聯(lián)之后 Path 內(nèi)容進行了更改卸例,則需要使用 setPath 方法重新關(guān)聯(lián)

該方法有兩個參數(shù)肌毅,第二個參數(shù)是用來確保 Path 閉合筷转,如果設(shè)置為 true, 則不論之前Path是否閉合悬而,都會自動閉合該 Path(如果Path可以閉合的話)呜舒。

在這里有兩點需要明確:

  • 1、不論 forceClosed 設(shè)置為何種狀態(tài)(true 或者 false)笨奠, 都不會影響原有Path的狀態(tài)袭蝗,即 Path 與 PathMeasure 關(guān)聯(lián)之后唤殴,之前的的 Path 不會有任何改變。
  • 2到腥、forceClosed 的設(shè)置狀態(tài)可能會影響測量結(jié)果朵逝,如果 Path 未閉合但在與 PathMeasure 關(guān)聯(lián)的時候設(shè)置 forceClosed 為 true 時,測量結(jié)果可能會比 Path 實際長度稍長一點乡范,獲取到到是該 Path 閉合時的狀態(tài)配名。

用一個例子來驗證一下:

canvas.translate(mViewWidth/2,mViewHeight/2);

Path path = new Path();
path.lineTo(0,200);
path.lineTo(200,200);
path.lineTo(200,0);

PathMeasure measure1 = new PathMeasure(path,false);
PathMeasure measure2 = new PathMeasure(path,true);

Log.e("TAG", "forceClosed=false---->"+measure1.getLength());
Log.e("TAG", "forceClosed=true----->"+measure2.getLength());

canvas.drawPath(path,mDeafultPaint);

log如下:

forceClosed=false---->600.0
forceClosed=true----->800.0

繪制在界面上的效果如下:

通過上面的示例能驗證以上兩個問題,另外還有:

  • 1晋辆、將 Path 與兩個的 PathMeasure 進行關(guān)聯(lián)渠脉,并給 forceClosed 設(shè)置了不同的狀態(tài),之后繪制再繪制出來的 Path 沒有任何變化瓶佳,所以與 Path 與 PathMeasure進行關(guān)聯(lián)并不會影響 Path 狀態(tài)芋膘。
  • 2、可以看到涩哟,設(shè)置 forceClosed 為 true 的方法比設(shè)置為 false 的方法測量出來的長度要長一點,這是由于 Path 沒有閉合的緣故盼玄,多出來的距離正是 Path 最后一個點與最開始一個點之間點距離贴彼。forceClosed 為 false 測量的是當(dāng)前 Path 狀態(tài)的長度, forceClosed 為 true埃儿,則不論Path是否閉合測量的都是 Path 的閉合長度器仗。

6.3.2 setPath、 isClosed 和 getLength

setPath 是 PathMeasure 與 Path 關(guān)聯(lián)的重要方法童番,效果和 構(gòu)造函數(shù) 中兩個參數(shù)的作用是一樣的精钮。

isClosed 用于判斷 Path 是否閉合,但是如果你在關(guān)聯(lián) Path 的時候設(shè)置 forceClosed 為 true 的話剃斧,這個方法的返回值則一定為true轨香。

getLength 用于獲取 Path 的總長度,在之前的測試中已經(jīng)用過了幼东。

6.3.3 getSegment

getSegment 用于獲取Path的一個片段臂容,方法如下:

boolean getSegment (float startD, float stopD, Path dst, boolean startWithMoveTo)

方法各參數(shù)釋義:

參數(shù) 作用 備注
返回值(boolean) 判斷截取是否成功 true 表示截取成功,結(jié)果存入dst中根蟹,false 截取失敗脓杉,不會改變dst中內(nèi)容
startD 開始截取位置距離 Path 起點的長度 取值范圍: 0 <= startD < stopD <= Path總長度
stopD 結(jié)束截取位置距離 Path 起點的長度 取值范圍: 0 <= startD < stopD <= Path總長度
dst 截取的 Path 將會添加到 dst 中 注意: 是添加,而不是替換
startWithMoveTo 起始點是否使用 moveTo 用于保證截取的 Path 第一個點位置不變
  • 如果 startD简逮、stopD 的數(shù)值不在取值范圍 [0, getLength] 內(nèi)球散,或者 startD == stopD 則返回值為 false,不會改變 dst 內(nèi)容散庶。
  • 如果在安卓4.4或者之前的版本蕉堰,在默認開啟硬件加速的情況下凌净,更改 dst 的內(nèi)容后可能繪制會出現(xiàn)問題,請關(guān)閉硬件加速或者給 dst 添加一個單個操作嘁灯,例如: dst.rLineTo(0, 0)

看看這個方法如何使用:

創(chuàng)建了一個 Path泻蚊, 并在其中添加了一個矩形,現(xiàn)在想截取矩形中的一部分丑婿,就是下圖中紅色的部分性雄。

代碼如下:

canvas.translate(mViewWidth / 2, mViewHeight / 2);          // 平移坐標(biāo)系

Path path = new Path();                                     // 創(chuàng)建Path并添加了一個矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
Path dst = new Path();                                      // 創(chuàng)建用于存儲截取后內(nèi)容的 Path

PathMeasure measure = new PathMeasure(path, false);         // 將 Path 與 PathMeasure 關(guān)聯(lián)
// 截取一部分存入dst中,并使用 moveTo 保持截取得到的 Path 第一個點的位置不變
measure.getSegment(200, 600, dst, true);                    

canvas.drawPath(dst, mDeafultPaint);                        // 繪制 dst

結(jié)果如下:

從上圖可以看到我們成功到將需要到片段截取了出來羹奉,然而當(dāng) dst 中有內(nèi)容時會怎樣呢秒旋?

canvas.translate(mViewWidth / 2, mViewHeight / 2);          // 平移坐標(biāo)系

Path path = new Path();                                     // 創(chuàng)建Path并添加了一個矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
Path dst = new Path();                                      // 創(chuàng)建用于存儲截取后內(nèi)容的 Path
dst.lineTo(-300, -300);                                     // <--- 在 dst 中添加一條線段

PathMeasure measure = new PathMeasure(path, false);         // 將 Path 與 PathMeasure 關(guān)聯(lián)
measure.getSegment(200, 600, dst, true);                   // 截取一部分 并使用 moveTo 保持截取得到的 Path 第一個點的位置不變

canvas.drawPath(dst, mDeafultPaint);                        // 繪制 Path

結(jié)果如下:

從上面的示例可以看到 dst 中的線段保留了下來,可以得到結(jié)論:被截取的 Path 片段會添加到 dst 中诀拭,而不是替換 dst 中到內(nèi)容迁筛。

前面兩個例子中 startWithMoveTo 均為 true, 如果設(shè)置為false會怎樣呢?

canvas.translate(mViewWidth / 2, mViewHeight / 2);          // 平移坐標(biāo)系

Path path = new Path();                                     // 創(chuàng)建Path并添加了一個矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
Path dst = new Path();                                      // 創(chuàng)建用于存儲截取后內(nèi)容的 Path
dst.lineTo(-300, -300);                                     // 在 dst 中添加一條線段

PathMeasure measure = new PathMeasure(path, false);         // 將 Path 與 PathMeasure 關(guān)聯(lián)
measure.getSegment(200, 600, dst, false);                   // <--- 截取一部分 不使用 startMoveTo, 保持 dst 的連續(xù)性

canvas.drawPath(dst, mDeafultPaint);                        // 繪制 Path

結(jié)果如下:

從該示例我們又可以得到一條結(jié)論:如果 startWithMoveTo 為 true, 則被截取出來到Path片段保持原狀耕挨,如果 startWithMoveTo 為 false细卧,則會將截取出來的 Path 片段的起始點移動到 dst 的最后一個點,以保證 dst 的連續(xù)性筒占。

6.3.4 nextContour

我們知道 Path 可以由多條曲線構(gòu)成贪庙,但不論是 getLength , getSegment 或者是其它方法,都只會在其中第一條線段上運行翰苫,而這個 nextContour 就是用于跳轉(zhuǎn)到下一條曲線的方法止邮,如果跳轉(zhuǎn)成功,則返回 true奏窑, 如果跳轉(zhuǎn)失敗导披,則返回 false。

如下埃唯,我們創(chuàng)建了一個 Path 并使其中包含了兩個閉合的曲線撩匕,內(nèi)部的邊長是200,外面的邊長是400墨叛,現(xiàn)在我們使用 PathMeasure 分別測量兩條曲線的總長度滑沧。

代碼:

canvas.translate(mViewWidth / 2, mViewHeight / 2);      // 平移坐標(biāo)系

Path path = new Path();
path.addRect(-100, -100, 100, 100, Path.Direction.CW);  // 添加小矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW);  // 添加大矩形

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

PathMeasure measure = new PathMeasure(path, false);     // 將Path與PathMeasure關(guān)聯(lián)
float len1 = measure.getLength();                       // 獲得第一條路徑的長度
measure.nextContour();                                  // 跳轉(zhuǎn)到下一條路徑
float len2 = measure.getLength();                       // 獲得第二條路徑的長度

Log.i("LEN","len1="+len1);                              // 輸出兩條路徑的長度
Log.i("LEN","len2="+len2);

log輸出結(jié)果:

len1=800.0
len2=1600.0

通過測試,我們可以得到以下內(nèi)容:

  • 1巍实、曲線的順序與 Path 中添加的順序有關(guān)滓技。
  • 2、getLength 獲取到到是當(dāng)前一條曲線分長度棚潦,而不是整個 Path 的長度令漂。
  • 3、getLength 等方法是針對當(dāng)前的曲線(其它方法請自行驗證)。

6.3.5 getPosTan

這個方法是用于得到路徑上某一長度的位置以及該位置的正切值:

boolean getPosTan (float distance, float[] pos, float[] tan)

各參數(shù)釋義:

參數(shù) 作用 備注
返回值(boolean) 判斷獲取是否成功 true表示成功叠必,數(shù)據(jù)會存入 pos 和 tan 中荚孵,
false 表示失敗,pos 和 tan 不會改變
distance 距離 Path 起點的長度 取值范圍: 0 <= distance <= getLength
pos 該點的坐標(biāo)值 當(dāng)前點在畫布上的位置纬朝,有兩個數(shù)值收叶,分別為x,y坐標(biāo)共苛。
tan 該點的正切值 當(dāng)前點在曲線上的方向判没,使用 Math.atan2(tan[1], tan[0]) 獲取到正切角的弧度值。

tan 是用來判斷 Path 上趨勢的隅茎,即在這個位置上曲線的走向吼虎,請看下圖示例潘悼,注意箭頭的方向:

點擊這里下載箭頭圖片

可以看到 上圖中箭頭在沿著 Path 運動時,方向始終與 Path 走向保持一致焦蘑,保持方向主要就是依靠 tan 巍扛。

下面來看看代碼是如何實現(xiàn)的琼讽,首先需要定義幾個必要的變量:

private float currentValue = 0;     // 用于紀(jì)錄當(dāng)前的位置,取值范圍[0,1]映射Path的整個長度
private float[] pos;                // 當(dāng)前點的實際位置
private float[] tan;                // 當(dāng)前點的tangent值,用于計算圖片所需旋轉(zhuǎn)的角度
private Bitmap mBitmap;             // 箭頭圖片
private Matrix mMatrix;             // 矩陣,用于對圖片進行一些操作

初始化這些變量(在構(gòu)造函數(shù)中調(diào)用):

private void init(Context context) {
      pos = new float[2];
      tan = new float[2];
      BitmapFactory.Options options = new BitmapFactory.Options();
      options.inSampleSize = 2;       // 縮放圖片
      mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrow, options);
      mMatrix = new Matrix();
}

具體繪制:

canvas.translate(mViewWidth / 2, mViewHeight / 2);      // 平移坐標(biāo)系

Path path = new Path();                                 // 創(chuàng)建 Path
path.addCircle(0, 0, 200, Path.Direction.CW);           // 添加一個圓形
PathMeasure measure = new PathMeasure(path, false);     // 創(chuàng)建 PathMeasure

currentValue += 0.005;                                  // 計算當(dāng)前的位置在總長度上的比例[0,1]
if (currentValue >= 1) {
      currentValue = 0;
}

measure.getPosTan(measure.getLength() * currentValue, pos, tan);        // 獲取當(dāng)前位置的坐標(biāo)以及趨勢

mMatrix.reset();                                                        // 重置Matrix
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI); // 計算圖片旋轉(zhuǎn)角度

mMatrix.postRotate(degrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);   // 旋轉(zhuǎn)圖片
mMatrix.postTranslate(pos[0] - mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight() / 2);   // 將圖片繪制中心調(diào)整到與當(dāng)前點重合

canvas.drawPath(path, mDeafultPaint);                                   // 繪制 Path
canvas.drawBitmap(mBitmap, mMatrix, mDeafultPaint);                     // 繪制箭頭

invalidate();                                                           // 重繪頁面

核心要點:

  • 1糠聪、圖片需要旋轉(zhuǎn)的角度應(yīng)該跟圓上改點的切線斜率有關(guān)郊艘,而切線夾角的tan值可以通過getPosTan得到,而Math中 atan2 方法是根據(jù)正切是數(shù)值計算出該角度的大小,得到的單位是弧度(取值范圍是 -pi 到 pi)出嘹,所以上面又將弧度轉(zhuǎn)為了角度席楚。
  • 2、通過 Matrix 來設(shè)置圖片對旋轉(zhuǎn)角度和位移疚漆,這里使用的方法與前面講解過對 canvas操作有些類似酣胀。
  • 3刁赦、頁面刷新娶聘,頁面刷新此處是在 onDraw 里面調(diào)用了 invalidate 方法來保持界面不斷循環(huán)刷新,但并不提倡這么做甚脉,正確對做法應(yīng)該是使用 線程 或者 ValueAnimator 來控制界面的刷新丸升。

6.3.6 getMatrix

這個方法是用于得到路徑上某一長度的位置以及該位置的正切值的矩陣:

boolean getMatrix (float distance, Matrix matrix, int flags)

各參數(shù)釋義:

參數(shù) 作用 備注
返回值(boolean) 判斷獲取是否成功 true表示成功,數(shù)據(jù)會存入matrix中牺氨,false 失敗狡耻,matrix內(nèi)容不會改變
distance 距離 Path 起點的長度 取值范圍: 0 <= distance <= getLength
matrix 根據(jù) falgs 封裝好的matrix 會根據(jù) flags 的設(shè)置而存入不同的內(nèi)容
flags 規(guī)定哪些內(nèi)容會存入到matrix中 可選擇POSITION_MATRIX_FLAG(位置)、ANGENT_MATRIX_FLAG(正切)

其實這個方法就相當(dāng)于在前一個例子中封裝 matrix 的過程猴凹,上面的過程由 getMatrix 來做了夷狰,可以直接得到一個封裝好到 matrix

最后到 flags 選項可以選擇 位置 或者 正切郊霎,如果兩個選項都想選擇沼头,可以將兩個選項之間用 | 連接起來,如下:

measure.getMatrix(distance, matrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);

試試將上面都例子中 getPosTan 替換為 getMatrix, 這樣一來就會顯得簡單很多:

具體繪制:

Path path = new Path();                                 // 創(chuàng)建 Path
path.addCircle(0, 0, 200, Path.Direction.CW);           // 添加一個圓形

PathMeasure measure = new PathMeasure(path, false);     // 創(chuàng)建 PathMeasure

currentValue += 0.005;                                  // 計算當(dāng)前的位置在總長度上的比例[0,1]
if (currentValue >= 1) {
      currentValue = 0;
}

// 獲取當(dāng)前位置的坐標(biāo)以及趨勢的矩陣
measure.getMatrix(measure.getLength() * currentValue, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
mMatrix.preTranslate(-mBitmap.getWidth() / 2, -mBitmap.getHeight() / 2);   // <-- 將圖片繪制中心調(diào)整到與當(dāng)前點重合(注意:此處是前乘pre)

canvas.drawPath(path, mDeafultPaint);                                   // 繪制 Path
canvas.drawBitmap(mBitmap, mMatrix, mDeafultPaint);                     // 繪制箭頭

invalidate();                                                           // 重繪頁面

使用 getMatrix 方法的確可以節(jié)省一些代碼进倍,不過這里依舊需要注意一些內(nèi)容:

  • 1土至、對 matrix 的操作必須要在 getMatrix 之后進行,否則會被 getMatrix 重置而導(dǎo)致無效猾昆。
  • 2陶因、矩陣對旋轉(zhuǎn)角度默認為圖片的左上角,此處需要使用 preTranslate 調(diào)整為圖片中心垂蜗。
  • 3楷扬、pre(矩陣前乘) 與 post(矩陣后乘) 的區(qū)別,后續(xù)文章講解么抗。

6.4 Path & SVG

當(dāng)圖形過于復(fù)雜時毅否,用代碼寫就不現(xiàn)實了,在繪制復(fù)雜的圖形時一般是將 SVG 圖像轉(zhuǎn)換為 Path蝇刀。

SVG 是一種矢量圖螟加,內(nèi)部用的是 xml 格式化存儲方式存儲這操作和數(shù)據(jù),可以將 SVG 看作是 Path 的各項操作簡化書寫后的存儲格式吞琐。

Path 和 SVG 結(jié)合通常能誕生出一些奇妙的東西捆探,如下:

該圖片來自這個開源庫 :PathView
SVG 轉(zhuǎn) Path 的解析可以用這個庫: AndroidSVG


6.5 Path使用技巧

先放一個效果圖,然后分析一下實現(xiàn)過程:

這是一個搜索的動效圖站粟,通過分析可以得到它應(yīng)該有四種狀態(tài)黍图,分別如下:

狀態(tài) 概述
初始狀態(tài) 初始狀態(tài),沒有任何動效奴烙,只顯示一個搜索標(biāo)志 ??
準(zhǔn)備搜索 放大鏡圖標(biāo)逐漸變化為一個點
正在搜索 圍繞這一個圓環(huán)運動助被,并且線段長度會周期性變化
準(zhǔn)備結(jié)束 從一個點逐漸變化成為放大鏡圖標(biāo)

這些狀態(tài)是有序轉(zhuǎn)換的,轉(zhuǎn)換流程以及轉(zhuǎn)換條件如下:

6.5.1 Path 劃分

為了制作對方便切诀,此處整個動效用了兩個 Path揩环, 一個是中間對放大鏡, 另一個則是外側(cè)的圓環(huán),將兩者全部畫出來是這樣的幅虑。

其中 Path 的走向要把握好丰滑,如下(只是一個放大鏡,并不是♂):

其中圓形上面的點可以用 PathMeasure 測量倒庵,無需計算褒墨。

6.5.2 動畫狀態(tài)與時間關(guān)聯(lián)

此處使用的是 ValueAnimator,它可以將一段時間映射到一段數(shù)值上擎宝,隨著時間變化不斷的更新數(shù)值郁妈,并且可以使用插值器來控制數(shù)值變化規(guī)律。

6.5.3 具體繪制

繪制部分是根據(jù) 當(dāng)前狀態(tài)以及從 ValueAnimator 獲得的數(shù)值來截取 Path 中合適的部分繪制出來绍申。

6.5.4 最終效果

6.5.5 源碼

戳這里查看源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末噩咪,一起剝皮案震驚了整個濱河市锄奢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剧腻,老刑警劉巖拘央,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異书在,居然都是意外死亡灰伟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門儒旬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栏账,“玉大人,你說我怎么就攤上這事栈源〉簿簦” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵甚垦,是天一觀的道長茶鹃。 經(jīng)常有香客問我,道長艰亮,這世上最難降的妖魔是什么闭翩? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮迄埃,結(jié)果婚禮上疗韵,老公的妹妹穿的比我還像新娘。我一直安慰自己侄非,他們只是感情好蕉汪,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逞怨,像睡著了一般者疤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骇钦,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天宛渐,我揣著相機與錄音竞漾,去河邊找鬼眯搭。 笑死,一個胖子當(dāng)著我的面吹牛业岁,可吹牛的內(nèi)容都是我干的鳞仙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼笔时,長吁一口氣:“原來是場噩夢啊……” “哼棍好!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤借笙,失蹤者是張志新(化名)和其女友劉穎扒怖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體业稼,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡盗痒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了低散。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俯邓。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖熔号,靈堂內(nèi)的尸體忽然破棺而出稽鞭,到底是詐尸還是另有隱情,我是刑警寧澤引镊,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布朦蕴,位于F島的核電站,受9級特大地震影響弟头,放射性物質(zhì)發(fā)生泄漏梦重。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一亮瓷、第九天 我趴在偏房一處隱蔽的房頂上張望琴拧。 院中可真熱鬧,春花似錦嘱支、人聲如沸蚓胸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沛膳。三九已至,卻和暖如春汛聚,著一層夾襖步出監(jiān)牢的瞬間锹安,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工倚舀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叹哭,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓痕貌,卻偏偏與公主長得像风罩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子舵稠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容