android之自定view之(八)----PathMeasure

PathMeasure是什么?

PathMeasure是用来对Path进行测量的,一般PathMeasure是和Path配合使用的,通过PathMeasure,我们可以知道Path路径上某个点的坐标、Path的长度等等.

构造方法:

方法名 释义
PathMeasure() 创建一个空的PathMeasure
athMeasure(Path path, boolean forceClosed) 创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。

公共方法:

返回值 方法名 释义
void setPath(Path path, boolean forceClosed) 关联一个Path
boolean isClosed() 是否闭合
float getLength() 获取Path的长度
boolean nextContour() 跳转到下一个轮廓
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 截取片段
boolean getPosTan(float distance, float[] pos, float[] tan) 获取指定长度的位置坐标及该点切线值
boolean getMatrix(float distance, Matrix matrix, int flags) 获取指定长度的位置坐标及该点Matrix

Android Demo

(1) 显示一个300*300的矩形

public class PathMeasureView extends View {

    private static final String TAG = "PathMeasureView";

    public PathMeasureView(Context context) {
        super(context);
        init(null, 0);
    }

    public PathMeasureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public PathMeasureView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(20f);
        Path sourcePath = new Path();
        sourcePath .addRect(300, 300, 600, 600, Path.Direction.CW);
        PathMeasure measure = new PathMeasure();
        measure.setPath(sourcePath , false);
        Log.e(TAG, "onDraw---measure.getLength() is " + measure.getLength());
        canvas.drawPath(sourcePath , paint);
    }

}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.readygo.pathmeasuredemo.PathMeasureView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

UI显示如图:
android之自定view之(八)----PathMeasure
log显示:

 E/PathMeasureView: onDraw---measure.getLength() is 1200.0

可以看到长度为矩形的周长1200=300*4

(2)显示一个300*300的矩形和截取一个从0到450长度的部分矩形

我们要显示如下效果图:
android之自定view之(八)----PathMeasure
关键代码:

        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(20f);

        Path sourcePath = new Path();
        sourcePath.addRect(300, 300, 600, 600, Path.Direction.CW);
        canvas.drawPath(sourcePath, paint);

        paint.setColor(Color.BLACK);
        PathMeasure measure = new PathMeasure();
        measure.setPath(sourcePath, false);
        Path dstPath = new Path();
        measure.getSegment(0, 450, dstPath, true);
        canvas.drawPath(dstPath, paint);

(3)我们添加一条直线和0到450的部分矩形图片
UI显示如图:
android之自定view之(八)----PathMeasure
具体实现逻辑:

        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(20f);
        Path sourcePath = new Path();
        sourcePath.addRect(300, 300, 600, 600, Path.Direction.CW);
        PathMeasure measure = new PathMeasure();
        measure.setPath(sourcePath, false);
        Path dstPath = new Path();
        //给dstPath添加一条直线
        dstPath.lineTo(200, 800);
        measure.getSegment(0, 450, dstPath, true);
        canvas.drawPath(dstPath, paint);

如果我们将代码修改为:

measure.getSegment(0, 450, dstPath, false); //ture----false

则UI显示为:
android之自定view之(八)----PathMeasure
(4) pathMeasure.getPosTan 实现动画
效果UI:
android之自定view之(八)----PathMeasure
实现逻辑:

public class PathMeasureView extends View {

    private static final String TAG = "PathMeasureView";

    private Paint mPaint;
    private Path  mPath;
    private PathMeasure pathMeasure;
    private RectF mRectF;
    private float[] pos;
    private float distance;
    private int radius=20;

    public PathMeasureView(Context context) {
        super(context);
        init(null, 0);
    }

    public PathMeasureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public PathMeasureView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(20f);
        mPath = new Path();
        pathMeasure = new PathMeasure();
        mRectF = new RectF(300,300,600,600);
        pos = new float[2];
        startMove();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.i(TAG,"onDraw--pos[0]:"+pos[0]+"--pos[1]:"+pos[1]);
        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], radius, mPaint);
    }

    public void startMove() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.setInterpolator(new DecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                distance = (float) animation.getAnimatedValue();
                Log.i(TAG,"onAnimationUpdate--distance:"+distance);
                pathMeasure.getPosTan(distance*pathMeasure.getLength(), pos, null);
                //postInvalidate();
                invalidate();
            }
        });
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setDuration(3000);
        animator.start();
    }

}

如果我们将矩形修改为一个圆形:

        //mPath.addRect(mRectF, Path.Direction.CW);
        mPath.addCircle(600,600,300, Path.Direction.CW);

效果如图:
android之自定view之(八)----PathMeasure

(5) pathMeasure.getPosTan 实现动画显示一个图片
效果图:
android之自定view之(八)----PathMeasure
实现逻辑为:

public class PathMeasureView extends View {

    private static final String TAG = "PathMeasureView";

    private Paint mPaint;
    private Path  mPath;
    private PathMeasure pathMeasure;
    private float[] pos;
    private float[] tan;
    private Bitmap mBitmap;
    private Matrix mMatrix;
    private float distance;

    public PathMeasureView(Context context) {
        super(context);
        init(null, 0,context);
    }

    public PathMeasureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0,context);
    }

    public PathMeasureView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle,context);
    }

    private void init(AttributeSet attrs, int defStyle,Context context) {
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(20f);
        mPath = new Path();
        pathMeasure = new PathMeasure();
        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();
        startMove();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.i(TAG,"onDraw--pos[0]:"+pos[0]+"--pos[1]:"+pos[1]);
        mPath.reset();
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        mPath.addCircle(600,600,300, Path.Direction.CW);
        pathMeasure.setPath(mPath, false);
        canvas.drawPath(mPath, mPaint);

        mMatrix.reset();
        float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
        mMatrix.postRotate(degrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
        mMatrix.postTranslate(pos[0]-mBitmap.getWidth()/2,pos[1]-mBitmap.getHeight()/2);
        canvas.drawBitmap(mBitmap, mMatrix, mPaint);
    }

    public void startMove() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.setInterpolator(new DecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                distance = (float) animation.getAnimatedValue();
                Log.i(TAG,"onAnimationUpdate--distance:"+distance);
                pathMeasure.getPosTan(distance*pathMeasure.getLength(), pos, tan);
                //postInvalidate();
                invalidate();
            }
        });
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setDuration(3000);
        animator.start();
    }

}

参考资料

1.Android 自定义View学习(十六)——PathMeasure学习
https://www.jianshu.com/p/ac1250bccd3b/
2.安卓自定义View进阶-PathMeasure
https://www.gcssloop.com/customview/Path_PathMeasure
3.PathMeasure之迷径追踪
https://www.jianshu.com/p/3efa5341abcc
4.Android Path测量工具:PathMeasure
https://www.jianshu.com/p/82afb9c2e959?from=jiantop.com