淺談Android PathMeasure詳解和應(yīng)用
PathMeasure,顧名思義,就是一個(gè)用來測(cè)量Path的類,主要有以下方法:
構(gòu)造方法
無參構(gòu)造方法:
PathMeasure()
創(chuàng)建一個(gè)空的PathMeasure,用這個(gè)構(gòu)造函數(shù)可創(chuàng)建一個(gè)空的 PathMeasure,但是使用之前需要先調(diào)用 setPath 方法來與 Path 進(jìn)行關(guān)聯(lián)。被關(guān)聯(lián)的 Path 必須是已經(jīng)創(chuàng)建好的,如果關(guān)聯(lián)之后 Path 內(nèi)容進(jìn)行了更改,則需要使用 setPath 方法重新關(guān)聯(lián)。
有參構(gòu)造方法
PathMeasure(Path path, boolean forceClosed)
該構(gòu)造函數(shù)是創(chuàng)建一個(gè) PathMeasure 并關(guān)聯(lián)一個(gè) Path, 其實(shí)和創(chuàng)建一個(gè)空的 PathMeasure 后調(diào)用 setPath 進(jìn)行關(guān)聯(lián)效果是一樣的,同樣,被關(guān)聯(lián)的 Path 也必須是已經(jīng)創(chuàng)建好的,如果關(guān)聯(lián)之后 Path 內(nèi)容進(jìn)行了更改,則需要使用 setPath 方法重新關(guān)聯(lián)。 該方法有兩個(gè)參數(shù),第一個(gè)參數(shù)自然就是被關(guān)聯(lián)的 Path 了,第二個(gè)參數(shù)是用來確保 Path 閉合,如果設(shè)置為 true, 則不論之前Path是否閉合,都會(huì)自動(dòng)閉合該 Path(如果Path可以閉合的話)。
這里需要說明以下forceClosed:
1)不論 forceClosed 設(shè)置為何種狀態(tài)(true 或者 false), 都不會(huì)影響原有Path的狀態(tài),即 Path 與 PathMeasure 關(guān)聯(lián)之后,之前的的 Path 不會(huì)有任何改變。
2)forceClosed 的設(shè)置狀態(tài)可能會(huì)影響測(cè)量結(jié)果,如果 Path 沒有閉合但在與 PathMeasure 關(guān)聯(lián)的時(shí)候設(shè)置 forceClosed 為 true 時(shí),測(cè)量結(jié)果可能會(huì)比 Path 實(shí)際長(zhǎng)度稍長(zhǎng)一點(diǎn),獲取得到的是該 Path 閉合時(shí)的狀態(tài)。
setPath
setPath(Path path, boolean forceClosed)方法就是關(guān)聯(lián)一個(gè)Path,需要預(yù)先創(chuàng)建好。
isClosed
isClosed方法用于判斷 Path 是否閉合,但是如果你在關(guān)聯(lián) Path 的時(shí)候設(shè)置 forceClosed 為 true 的話,這個(gè)方法的返回值則一定為true。
getLength
getLength()方法用于獲取Path的長(zhǎng)度。
public class PathMeasureView extends View {
private static final String TAG = "lwj";
private int mViewHeight;
private int mViewWidth;
private Paint paint;
public PathMeasureView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(mViewWidth/2, mViewHeight/2);
Path path = new Path();
path.lineTo(0, 300);
path.lineTo(300, 300);
path.lineTo(300, 0);
PathMeasure measure = new PathMeasure(path, false);
PathMeasure measure2 = new PathMeasure(path, true);
Log.i(TAG, "length:"+measure.getLength());//900
Log.i(TAG,"length:"+ measure2.getLength());//1200
canvas.drawPath(path, paint);
}
//該方法在當(dāng)前View尺寸變化時(shí)被調(diào)用
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewHeight = h;
mViewWidth = w;
}
}
nextContour
我們知道 Path 可以由多條曲線構(gòu)成,但不論是 getLength , getgetSegment 或者是其它方法,都只會(huì)在其中第一條線段上運(yùn)行,而這個(gè) nextContour 就是用于跳轉(zhuǎn)到下一條曲線到方法,如果跳轉(zhuǎn)成功,則返回 true, 如果跳轉(zhuǎn)失敗,則返回 false。 注意:使用多路徑的效果需要關(guān)閉硬件加速。
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Path path = new Path();
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
path.addRect(-100, -100, 100, 100, Path.Direction.CW);
PathMeasure measure = new PathMeasure(path, false);
float length = measure.getLength();
//獲取下一個(gè)路徑,有可能沒有多個(gè)路徑了,返回false
boolean nextContour = measure.nextContour();
float length2 = measure.getLength();
Log.i("damon", "length1:"+length);
Log.i("damon", "length2:"+length2);
canvas.drawPath(path, paint);
getSegment
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo):用于獲取Path的一個(gè)片段。
解析:
1)返回值(boolean):判斷截取是否成功,true 表示截取成功,結(jié)果存入dst中,false 截取失敗,不會(huì)改變dst中內(nèi)容。
2)startD:開始截取位置距離 Path 起點(diǎn)的長(zhǎng)度 取值范圍: 0 <= startD < stopD <= Path總長(zhǎng)度;
3)stopD:結(jié)束截取位置距離 Path 起點(diǎn)的長(zhǎng)度 取值范圍: 0 <= startD < stopD <= Path總長(zhǎng)度;
4)dst:截取的 Path 將會(huì)添加到 dst 中 注意: 是添加,而不是替換;
5)startWithMoveTo:起始點(diǎn)是否使用 moveTo,用于保證截取的 Path 第一個(gè)點(diǎn)位置不變。
注意:
• 如果 startD、stopD 的數(shù)值不在取值范圍 [0, getLength] 內(nèi),或者 startD == stopD 則返回值為 false,不會(huì)改變 dst 內(nèi)容。
• 如果在安卓4.4或者之前的版本,在默認(rèn)開啟硬件加速的情況下,更改 dst 的內(nèi)容后可能繪制會(huì)出現(xiàn)問題,請(qǐng)關(guān)閉硬件加速或者給 dst 添加一個(gè)單個(gè)操作,例如: dst.rLineTo(0, 0)
• 可以用以下規(guī)則來判斷 startWithMoveTo 的取值:
true:保證截取得到的 Path 片段不會(huì)發(fā)生形變;
false:保證存儲(chǔ)截取片段的 Path(dst) 的連續(xù)性。
Path path = new Path();
//多路徑的效果需要關(guān)閉硬件加速??!
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
PathMeasure measure = new PathMeasure(path, false);
float length = measure.getLength();
Log.i("damon", "length1:"+length);
canvas.drawPath(path, paint);
Path dst = new Path();
dst.lineTo(-300, -300);
//startWithMoveTo:false,代表該起始點(diǎn)是否位上一個(gè)的結(jié)束點(diǎn)(是否保持連續(xù)性)。
measure.getSegment(200, 600, dst , false);
paint.setColor(Color.RED);
canvas.drawPath(dst, paint);
getMatrix
getMatrix(float distance, Matrix matrix, int flags):獲取路徑上某一長(zhǎng)度的位置以及該位置的正切值的矩陣。
返回值(boolean):判斷獲取是否成功,true表示成功,數(shù)據(jù)會(huì)存入matrix中,false 失敗,matrix內(nèi)容不會(huì)改變;
distance:距離 Path 起點(diǎn)的長(zhǎng)度,取值范圍: 0 <= distance <= getLength;
matrix:根據(jù) falgs 封裝好的matrix 會(huì)根據(jù) flags 的設(shè)置而存入不同的內(nèi)容;
flags:規(guī)定哪些內(nèi)容會(huì)存入到matrix中,可選擇:
POSITION_MATRIX_FLAG(位置)
ANGENT_MATRIX_FLAG(正切)
getPosTan
getPosTan(float distance, float[] pos, float[] tan):獲取指定長(zhǎng)度的位置坐標(biāo)及該點(diǎn)切線值tangle。
返回值(boolean):判斷獲取是否成功,true表示成功,數(shù)據(jù)會(huì)存入 pos 和 tan 中, false 表示失敗,pos 和 tan 不會(huì)改變;
distance:距離 Path 起點(diǎn)的長(zhǎng)度,取值范圍: 0 <= distance <= getLength;
pos:該點(diǎn)的坐標(biāo)值,坐標(biāo)值: (x==[0], y==[1]);
tan:該點(diǎn)的正切值,正切值: (x==[0], y==[1])。
通過 三角函數(shù)tan 得值計(jì)算出圖片旋轉(zhuǎn)的角度,tan 是 tangent 的縮寫, 其中tan0是鄰邊邊長(zhǎng),tan1是對(duì)邊邊長(zhǎng),而Math中 atan2 方法是根據(jù)正切是數(shù)值計(jì)算出該角度的大小,得到的單位是弧度,所以上面又將弧度轉(zhuǎn)為了角度。
Path path = new Path();
path.addCircle(0, 0, 300, Path.Direction.CW);
PathMeasure measure = new PathMeasure(path, false);
float[] pos = new float[2];
float[] tan = new float[2];//tan=y/x
measure.getPosTan(measure.getLength()/4, pos , tan );
Log.i("damon", "position:x-"+pos[0]+", y-"+pos[1]);
Log.i("damon", "tan:x-"+tan[0]+", y-"+tan[1]);
canvas.drawPath(path, paint);
應(yīng)用
繪制一個(gè)放大鏡,然后慢慢沿著放大鏡的路徑慢慢撤退消失,變成圓形搜索的loading,接著loading完成之后,沿著路徑繪制出放大鏡。 如效果圖所示:

這樣一個(gè)自定義View,需要用到PathMeasure,動(dòng)畫等知識(shí)配合來做。
public class SearchView extends View {
// 畫筆
private Paint mPaint;
// View 寬高
private int mViewWidth;
private int mViewHeight;
// 這個(gè)視圖擁有的狀態(tài)
public static enum State {
NONE,
STARTING,
SEARCHING,
ENDING
}
// 當(dāng)前的狀態(tài)(非常重要)
private State mCurrentState = State.NONE;
// 放大鏡與外部圓環(huán)
private Path path_srarch;
private Path path_circle;
// 測(cè)量Path 并截取部分的工具
private PathMeasure mMeasure;
// 默認(rèn)的動(dòng)效周期 2s
private int defaultDuration = 2000;
// 控制各個(gè)過程的動(dòng)畫
private ValueAnimator mStartingAnimator;
private ValueAnimator mSearchingAnimator;
private ValueAnimator mEndingAnimator;
// 動(dòng)畫數(shù)值(用于控制動(dòng)畫狀態(tài),因?yàn)橥粫r(shí)間內(nèi)只允許有一種狀態(tài)出現(xiàn),具體數(shù)值處理取決于當(dāng)前狀態(tài))
private float mAnimatorValue = 0;
// 動(dòng)效過程監(jiān)聽器
private ValueAnimator.AnimatorUpdateListener mUpdateListener;
private Animator.AnimatorListener mAnimatorListener;
// 用于控制動(dòng)畫狀態(tài)轉(zhuǎn)換
private Handler mAnimatorHandler;
// 判斷是否已經(jīng)搜索結(jié)束
private boolean isOver = false;
private int count = 0;
public SearchView(Context context) {
super(context);
initPaint();
initPath();
initListener();
initHandler();
initAnimator();
// 進(jìn)入開始動(dòng)畫
mCurrentState = State.STARTING;
mStartingAnimator.start();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(15);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setAntiAlias(true);
}
private void initPath() {
path_srarch = new Path();
path_circle = new Path();
mMeasure = new PathMeasure();
// 注意,不要到360度,否則內(nèi)部會(huì)自動(dòng)優(yōu)化,測(cè)量不能取到需要的數(shù)值
RectF oval1 = new RectF(-50, -50, 50, 50); // 放大鏡圓環(huán)
path_srarch.addArc(oval1, 45, 359.9f);
RectF oval2 = new RectF(-100, -100, 100, 100); // 外部圓環(huán)
path_circle.addArc(oval2, 45, -359.9f);
float[] pos = new float[2];
mMeasure.setPath(path_circle, false); // 放大鏡把手的位置
mMeasure.getPosTan(0, pos, null);
path_srarch.lineTo(pos[0], pos[1]); // 放大鏡把手
Log.i("TAG", "pos=" + pos[0] + ":" + pos[1]);
}
private void initListener() {
mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimatorValue = (float) animation.getAnimatedValue();
invalidate();
}
};
mAnimatorListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
// getHandle發(fā)消息通知?jiǎng)赢嫚顟B(tài)更新
mAnimatorHandler.sendEmptyMessage(0);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
};
}
private void initHandler() {
mAnimatorHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (mCurrentState) {
case STARTING:
// 從開始動(dòng)畫轉(zhuǎn)換好搜索動(dòng)畫
isOver = false;
mCurrentState = State.SEARCHING;
mStartingAnimator.removeAllListeners();
mSearchingAnimator.start();
break;
case SEARCHING:
if (!isOver) { // 如果搜索未結(jié)束 則繼續(xù)執(zhí)行搜索動(dòng)畫
mSearchingAnimator.start();
Log.e("Update", "RESTART");
count++;
if (count>2){ // count大于2則進(jìn)入結(jié)束狀態(tài)
isOver = true;
}
} else { // 如果搜索已經(jīng)結(jié)束 則進(jìn)入結(jié)束動(dòng)畫
mCurrentState = State.ENDING;
mEndingAnimator.start();
}
break;
case ENDING:
// 從結(jié)束動(dòng)畫轉(zhuǎn)變?yōu)闊o狀態(tài)
mCurrentState = State.NONE;
break;
}
}
};
}
private void initAnimator() {
mStartingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
mSearchingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
mEndingAnimator = ValueAnimator.ofFloat(1, 0).setDuration(defaultDuration);
mStartingAnimator.addUpdateListener(mUpdateListener);
mSearchingAnimator.addUpdateListener(mUpdateListener);
mEndingAnimator.addUpdateListener(mUpdateListener);
mStartingAnimator.addListener(mAnimatorListener);
mSearchingAnimator.addListener(mAnimatorListener);
mEndingAnimator.addListener(mAnimatorListener);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawSearch(canvas);
}
private void drawSearch(Canvas canvas) {
mPaint.setColor(Color.WHITE);
canvas.translate(mViewWidth / 2, mViewHeight / 2);
canvas.drawColor(Color.parseColor("#0082D7"));
switch (mCurrentState) {
case NONE:
canvas.drawPath(path_srarch, mPaint);
break;
case STARTING:
mMeasure.setPath(path_srarch, false);
Path dst = new Path();
mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst, true);
canvas.drawPath(dst, mPaint);
break;
case SEARCHING:
mMeasure.setPath(path_circle, false);
Path dst2 = new Path();
float stop = mMeasure.getLength() * mAnimatorValue;
float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * 200f));
// float start = stop-50;
mMeasure.getSegment(start, stop, dst2, true);
canvas.drawPath(dst2, mPaint);
break;
case ENDING:
mMeasure.setPath(path_srarch, false);
Path dst3 = new Path();
mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst3, true);
canvas.drawPath(dst3, mPaint);
break;
}
}
}
以上就是關(guān)于PathMeasure的詳解和應(yīng)用,需要讀者去多點(diǎn)動(dòng)手才能理解其中的精髓的地方。
相關(guān)文章
Android中文件讀寫(輸入流和輸出流)操作小結(jié)
這篇文章主要介紹了Android中文件讀寫(輸入流和輸出流)操作小結(jié),本文總結(jié)了Android中文件讀寫的原理、字節(jié)流和字符流的區(qū)別、文件讀寫的步驟、輸入流和輸出流以及代碼實(shí)例等內(nèi)容,需要的朋友可以參考下2015-06-06
Android編程之防止反編譯的實(shí)現(xiàn)方法
這篇文章主要介紹了Android編程之防止反編譯的實(shí)現(xiàn)方法,涉及Android針對(duì)運(yùn)行環(huán)境、簽名及程序相關(guān)信息的獲取與判定技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Android實(shí)現(xiàn)上拉加載更多ListView(PulmListView)
這篇文章主要介紹了Android實(shí)現(xiàn)上拉加載更多ListView:PulmListView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android自定義View軟鍵盤實(shí)現(xiàn)搜索
本文給大家分享android自定義view軟鍵盤實(shí)現(xiàn)搜索,對(duì)android軟鍵盤相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2015-12-12
android 有阻尼下拉刷新列表的實(shí)現(xiàn)方法
下面小編就為大家分享一篇android 有阻尼下拉刷新列表的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,一起跟隨小編過來看看吧2018-01-01
Android checkbox的listView(多選,全選,反選)具體實(shí)現(xiàn)方法
由于listview的一些特性,剛開始寫這種需求的功能的時(shí)候都會(huì)碰到一些問題,重點(diǎn)就是存儲(chǔ)每個(gè)checkbox的狀態(tài)值,在這里分享出了完美解決方法:2013-06-06
基于RxJava框架實(shí)現(xiàn)獲取驗(yàn)證碼的輔助類
這篇文章主要為大家詳細(xì)介紹了基于RxJava框架實(shí)現(xiàn)獲取驗(yàn)證碼的輔助類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
Android開發(fā)之多線程中實(shí)現(xiàn)利用自定義控件繪制小球并完成小球自動(dòng)下落功能實(shí)例
這篇文章主要介紹了Android開發(fā)之多線程中實(shí)現(xiàn)利用自定義控件繪制小球并完成小球自動(dòng)下落功能的方法,涉及Android多線程編程及圖形繪制相關(guān)技巧,需要的朋友可以參考下2015-12-12

