您好,登錄后才能下訂單哦!
PathMeasure,顧名思義,就是一個用來測量Path的類,主要有以下方法:
構造方法
無參構造方法:
PathMeasure()
創建一個空的PathMeasure,用這個構造函數可創建一個空的 PathMeasure,但是使用之前需要先調用 setPath 方法來與 Path 進行關聯。被關聯的 Path 必須是已經創建好的,如果關聯之后 Path 內容進行了更改,則需要使用 setPath 方法重新關聯。
有參構造方法
PathMeasure(Path path, boolean forceClosed)
該構造函數是創建一個 PathMeasure 并關聯一個 Path, 其實和創建一個空的 PathMeasure 后調用 setPath 進行關聯效果是一樣的,同樣,被關聯的 Path 也必須是已經創建好的,如果關聯之后 Path 內容進行了更改,則需要使用 setPath 方法重新關聯。 該方法有兩個參數,第一個參數自然就是被關聯的 Path 了,第二個參數是用來確保 Path 閉合,如果設置為 true, 則不論之前Path是否閉合,都會自動閉合該 Path(如果Path可以閉合的話)。
這里需要說明以下forceClosed:
1)不論 forceClosed 設置為何種狀態(true 或者 false), 都不會影響原有Path的狀態,即 Path 與 PathMeasure 關聯之后,之前的的 Path 不會有任何改變。
2)forceClosed 的設置狀態可能會影響測量結果,如果 Path 沒有閉合但在與 PathMeasure 關聯的時候設置 forceClosed 為 true 時,測量結果可能會比 Path 實際長度稍長一點,獲取得到的是該 Path 閉合時的狀態。
setPath
setPath(Path path, boolean forceClosed)方法就是關聯一個Path,需要預先創建好。
isClosed
isClosed方法用于判斷 Path 是否閉合,但是如果你在關聯 Path 的時候設置 forceClosed 為 true 的話,這個方法的返回值則一定為true。
getLength
getLength()方法用于獲取Path的長度。
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); } //該方法在當前View尺寸變化時被調用 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mViewHeight = h; mViewWidth = w; } }
nextContour
我們知道 Path 可以由多條曲線構成,但不論是 getLength , getgetSegment 或者是其它方法,都只會在其中第一條線段上運行,而這個 nextContour 就是用于跳轉到下一條曲線到方法,如果跳轉成功,則返回 true, 如果跳轉失敗,則返回 false。 注意:使用多路徑的效果需要關閉硬件加速。
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(); //獲取下一個路徑,有可能沒有多個路徑了,返回false boolean nextContour = measure.nextContour(); float length3 = measure.getLength(); Log.i("damon", "length2:"+length); Log.i("damon", "length3:"+length3); canvas.drawPath(path, paint);
getSegment
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo):用于獲取Path的一個片段。
解析:
1)返回值(boolean):判斷截取是否成功,true 表示截取成功,結果存入dst中,false 截取失敗,不會改變dst中內容。
2)startD:開始截取位置距離 Path 起點的長度 取值范圍: 0 <= startD < stopD <= Path總長度;
3)stopD:結束截取位置距離 Path 起點的長度 取值范圍: 0 <= startD < stopD <= Path總長度;
4)dst:截取的 Path 將會添加到 dst 中 注意: 是添加,而不是替換;
5)startWithMoveTo:起始點是否使用 moveTo,用于保證截取的 Path 第一個點位置不變。
注意:
• 如果 startD、stopD 的數值不在取值范圍 [0, getLength] 內,或者 startD == stopD 則返回值為 false,不會改變 dst 內容。
• 如果在安卓4.4或者之前的版本,在默認開啟硬件加速的情況下,更改 dst 的內容后可能繪制會出現問題,請關閉硬件加速或者給 dst 添加一個單個操作,例如: dst.rLineTo(0, 0)
• 可以用以下規則來判斷 startWithMoveTo 的取值:
true:保證截取得到的 Path 片段不會發生形變;
false:保證存儲截取片段的 Path(dst) 的連續性。
Path path = new Path(); //多路徑的效果需要關閉硬件加速!! path.addRect(-200, -200, 200, 200, Path.Direction.CW); PathMeasure measure = new PathMeasure(path, false); float length = measure.getLength(); Log.i("damon", "length2:"+length); canvas.drawPath(path, paint); Path dst = new Path(); dst.lineTo(-300, -300); //startWithMoveTo:false,代表該起始點是否位上一個的結束點(是否保持連續性)。 measure.getSegment(200, 600, dst , false); paint.setColor(Color.RED); canvas.drawPath(dst, paint);
getMatrix
getMatrix(float distance, Matrix matrix, int flags):獲取路徑上某一長度的位置以及該位置的正切值的矩陣。
返回值(boolean):判斷獲取是否成功,true表示成功,數據會存入matrix中,false 失敗,matrix內容不會改變;
distance:距離 Path 起點的長度,取值范圍: 0 <= distance <= getLength;
matrix:根據 falgs 封裝好的matrix 會根據 flags 的設置而存入不同的內容;
flags:規定哪些內容會存入到matrix中,可選擇:
POSITION_MATRIX_FLAG(位置)
ANGENT_MATRIX_FLAG(正切)
getPosTan
getPosTan(float distance, float[] pos, float[] tan):獲取指定長度的位置坐標及該點切線值tangle。
返回值(boolean):判斷獲取是否成功,true表示成功,數據會存入 pos 和 tan 中, false 表示失敗,pos 和 tan 不會改變;
distance:距離 Path 起點的長度,取值范圍: 0 <= distance <= getLength;
pos:該點的坐標值,坐標值: (x==[0], y==[1]);
tan:該點的正切值,正切值: (x==[0], y==[1])。
通過 三角函數tan 得值計算出圖片旋轉的角度,tan 是 tangent 的縮寫, 其中tan0是鄰邊邊長,tan1是對邊邊長,而Math中 atan2 方法是根據正切是數值計算出該角度的大小,得到的單位是弧度,所以上面又將弧度轉為了角度。
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);
應用
繪制一個放大鏡,然后慢慢沿著放大鏡的路徑慢慢撤退消失,變成圓形搜索的loading,接著loading完成之后,沿著路徑繪制出放大鏡。 如效果圖所示:
這樣一個自定義View,需要用到PathMeasure,動畫等知識配合來做。
public class SearchView extends View { // 畫筆 private Paint mPaint; // View 寬高 private int mViewWidth; private int mViewHeight; // 這個視圖擁有的狀態 public static enum State { NONE, STARTING, SEARCHING, ENDING } // 當前的狀態(非常重要) private State mCurrentState = State.NONE; // 放大鏡與外部圓環 private Path path_srarch; private Path path_circle; // 測量Path 并截取部分的工具 private PathMeasure mMeasure; // 默認的動效周期 2s private int defaultDuration = 2000; // 控制各個過程的動畫 private ValueAnimator mStartingAnimator; private ValueAnimator mSearchingAnimator; private ValueAnimator mEndingAnimator; // 動畫數值(用于控制動畫狀態,因為同一時間內只允許有一種狀態出現,具體數值處理取決于當前狀態) private float mAnimatorValue = 0; // 動效過程監聽器 private ValueAnimator.AnimatorUpdateListener mUpdateListener; private Animator.AnimatorListener mAnimatorListener; // 用于控制動畫狀態轉換 private Handler mAnimatorHandler; // 判斷是否已經搜索結束 private boolean isOver = false; private int count = 0; public SearchView(Context context) { super(context); initPaint(); initPath(); initListener(); initHandler(); initAnimator(); // 進入開始動畫 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度,否則內部會自動優化,測量不能取到需要的數值 RectF oval1 = new RectF(-50, -50, 50, 50); // 放大鏡圓環 path_srarch.addArc(oval1, 45, 359.9f); RectF oval2 = new RectF(-100, -100, 100, 100); // 外部圓環 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發消息通知動畫狀態更新 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: // 從開始動畫轉換好搜索動畫 isOver = false; mCurrentState = State.SEARCHING; mStartingAnimator.removeAllListeners(); mSearchingAnimator.start(); break; case SEARCHING: if (!isOver) { // 如果搜索未結束 則繼續執行搜索動畫 mSearchingAnimator.start(); Log.e("Update", "RESTART"); count++; if (count>2){ // count大于2則進入結束狀態 isOver = true; } } else { // 如果搜索已經結束 則進入結束動畫 mCurrentState = State.ENDING; mEndingAnimator.start(); } break; case ENDING: // 從結束動畫轉變為無狀態 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; } } }
以上就是關于PathMeasure的詳解和應用,需要讀者去多點動手才能理解其中的精髓的地方。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。