Android Path測量工具:PathMeasure

_小馬快跑_發表於2017-12-15
  • PathMeasure是什麼? 顧名思義,PathMeasure是用來對Path進行測量的,一般PathMeasure是和Path配合使用的,通過PathMeasure,我們可以知道Path路徑上某個點的座標、Path的長度等等,如果對Path不瞭解,可以先看下這篇文章:Android Canvas之Path操作

PathMeasure有兩個建構函式:

//構建一個空的PathMeasure
PathMeasure() 
//構建一個PathMeasure並關聯一個指定的建立好的Path
PathMeasure(Path path, boolean forceClosed) 
複製程式碼

說明:無參建構函式PathMeasure()在使用前必須呼叫 setPath(Path path, boolean forceClosed)來關聯一個Path,等同於有引數的建構函式PathMeasure(Path path, boolean forceClosed),如果關聯之後的Path有改變,需要呼叫 setPath(Path path, boolean forceClosed)重新關聯。

PathMeasure常用方法:

setPath(Path path, boolean forceClosed) 
isClosed()
getLength()
getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
getMatrix(float distance, Matrix matrix, int flags)
getPosTan(float distance, float pos[], float tan[])
nextContour()
複製程式碼
  • setPath(Path path, boolean forceClosed) PathMeasure關聯一個建立好的Path,如果第二個引數forceClosed為true,並且關聯的Path未閉合時,測量的Path長度可能會比Path的實際長度長一點,因為測量的是Path閉合的長度,但關聯的Path不會有任何變化。

  • isClosed() 判斷關聯的Path是否是閉合狀態,若forceClosed為true,則此方法一定返回true。

  • getLength() 返回已關聯Path的總長度,若setPath()時設定的forceClosed為true,則返回值可能會比實際長度長。

  • getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 擷取Path的一段,如果擷取成功,返回true;反之返回false。

引數 備註
startD 起點在Path的位置,取值範圍0<=startD<stopD<=getLength()
stopD 終點在Path的位置,取值範圍0<=startD<stopD<=getLength()
dst 將擷取的path的片段新增到dst中
startWithMoveTo 起點是否使用MoveTo,如果為true,則擷取的path的第一個點不會變化,擷取的path也不會改變,如果為false,則擷取的path可能會發生形變。

注: 1、如果擷取的Path的長度為0,則返回false,大於0則返回true; 2、startD、stopD必須為合法值(0,getLength()),如果startD>=stopD,則返回false; 3、在4.4或之前的版本,在開啟硬體加速時,繪製可能會不顯示,請關閉硬體加速或者給 dst 新增一個簡單操作,如: dst.rLineTo(0, 0)

示例:

//初始化Paint
  Paint paint = new Paint();
  paint.setColor(Color.RED);
  paint.setStyle(Paint.Style.STROKE);
  paint.setStrokeWidth(10f);
  //初始化Path並順時針繪製一個矩形
  Path sourcePath = new Path();
  sourcePath .addRect(300, 300, 600, 600, Path.Direction.CW);
  PathMeasure measure = new PathMeasure();
  measure.setPath(sourcePath , false);
  //列印Path的長度
  Log.e("TTT", "measure.getLength() is " + measure.getLength());
  canvas.drawPath(sourcePath , paint);
複製程式碼

結果:

org.ninetripods.mq.pathmeasure E/TTT: measure.getLength() is 1200.0
複製程式碼

C912D0F205BEF3C3BB3022AD90EA91D5_副本.jpg

現在想擷取下圖中Path的黑色部分並放到一個新的Path中:

getSeg.jpg

       //初始化Paint
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(10f);
        //初始化Path並順時針繪製一個矩形
        Path sourcePath = new Path();
        sourcePath.addRect(300, 300, 600, 600, Path.Direction.CW);
        PathMeasure measure = new PathMeasure();
        measure.setPath(sourcePath, false);
        //初始化一個空的Path
        Path dstPath = new Path();
        //擷取sourcePath的一部分新增到dstPath中
        measure.getSegment(450, 900, dstPath, true);
        canvas.drawPath(dstPath, paint);
複製程式碼

效果圖:

seg.png

上面程式碼中dstPath初始化時是沒有內容的,如果dstPath本身有內容呢

        //初始化Paint
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(10f);
        //初始化Path並順時針繪製一個矩形
        Path sourcePath = new Path();
        sourcePath.addRect(300, 300, 600, 600, Path.Direction.CW);
        PathMeasure measure = new PathMeasure();
        measure.setPath(sourcePath, false);
        //初始化一個空的Path
        Path dstPath = new Path();
        //給dstPath新增一條直線
        dstPath.lineTo(800, 200);
        //擷取sourcePath的一部分新增到dstPath中
        measure.getSegment(450, 900, dstPath, true);
        canvas.drawPath(dstPath, paint);
複製程式碼

效果圖:

seg1.png

上面呼叫getSegment()時引數startWithMoveTo設成false會成什麼樣呢,上面程式碼中改變一行程式碼

measure.getSegment(450, 900, dstPath, false);
複製程式碼

效果圖:

seg2.png

可見,startWithMoveTo為true時,被擷取的path片段會保持原狀;startWithMoveTo為false時,會將擷取的path片段的起始點移動到dstPath的終點以保持dstPath的連續性。

  • getMatrix(float distance, Matrix matrix, int flags) 距離Path起始點的一段長度distance,通過計算得到該位置座標並返回一個處理好的矩陣,該矩陣以左上角為旋轉點,如果Path不存在或者長度為0,該方法返回false。
引數 備註
distance 距離Path起始點的距離,取值範圍0 <= distance <= getLength()
matrix 根據 flags封裝matrix,flags不同,存入matrix的就不同
flags PathMeasure.POSITION_MATRIX_FLAG:位置資訊 ,PathMeasure.TANGENT_MATRIX_FLAG:切邊資訊,方位角資訊,使得圖片按path旋轉。

//TODO 關於Matrix下一篇詳細介紹。

  • getPosTan(float distance, float pos[], float tan[]) 距離Path起始點的長度distance,通過計算返回該長度在Path上的座標及該座標的正切值並分別賦值給pos[]、tan[]。
引數 備註
distance 距離Path起始點的距離,取值範圍0 <= distance <= getLength()
pos[] distance在path上的座標,即pos[]存的該點的座標x,y值
tan[] distance在path上對應座標點在path上的方向,tan[0]是鄰邊邊長,tan[1]是對邊邊長。通過Math.atan2(tan[1], tan[0])*180.0/Math.PI 可以得到正切角的弧度值。

示例,先看下效果圖:

path.gif

來看下主要程式碼,完整程式碼已上傳到github:PathMeasure

    @Override
    protected void onDraw(Canvas canvas) {
        //繪製矩形
        mPath.reset();
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        mPath.addRect(mRectF, Path.Direction.CW);
        pathMeasure.setPath(mPath, false);
        canvas.drawPath(mPath, mPaint);
        //繪製圓
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(pos[0], pos[1], 12, mPaint);
    }

    public void startMove() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, pathMeasure.getLength());
        animator.setDuration(3 * 1000);
        animator.setInterpolator(new DecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float distance = (float) animation.getAnimatedValue();
                pathMeasure.getPosTan(distance, pos, null);
                postInvalidate();
            }
        });
        animator.start();
    }
複製程式碼

原理是在呼叫startMove()後,通過ValueAnimator.ofFloat(0, pathMeasure.getLength())及animation.getAnimatedValue()來不斷獲得圓距離path起點的距離,通過getPosTan(distance, pos, null)不斷獲得圓在path上的座標並重新整理介面。

  • nextContour() 如果Path由多條曲線組成且彼此不連線,則getLength getSegment getMatrix getPosTan方法都是針對當前正在操作的,比如:如果Path由多條曲線組成且彼此不連線,getLength()返回的只是當前操作的曲線的長度,並不是所有曲線的長度,那麼怎麼跳轉到下一條曲線上呢?答案就是用nextContour(),跳轉成功返回true;否則返回false。
Path path = new Path();
PathMeasure measure = new PathMeasure();
//繪製一條從(100,100)到(900,100)的直線,長度為800
path.moveTo(100, 100);
path.lineTo(900, 100);
//繪製一條從(100,200)到(500,100)的直線,長度為400
path.moveTo(100, 200);
path.lineTo(500, 200);
measure.setPath(path, false);
//輸出第一條路徑的長度
Log.e("TTT", "measure.getLength() is " + measure.getLength());
measure.nextContour();
//輸出第二條路徑的長度
Log.e("TTT", "measure.getLength() is " + measure.getLength());
canvas.drawPath(path, paint);
複製程式碼

Log日誌:

02-09 18:02:50.483 9010-9010/? E/TTT: measure.getLength() is 800.0
02-09 18:02:50.483 9010-9010/? E/TTT: measure.getLength() is 400.0
複製程式碼

相關文章