PathMeasure

PathMeasure是Android中用于测量和获取Path信息的类。本文详细介绍了PathMeasure的构造方法、getSegment、nextContour、getPosTan和getMatrix等关键方法的用法,包括参数解释和注意事项,帮助开发者更好地理解和应用PathMeasure。

PathMeasure是一个用来测量Path的类,主要有以下方法

构造方法

/**
 * 创建一个空的pathMeasure
 */
PathMeasure()
/**
 * 创建一个pathMeasure 并关联一个指定的path(path岁要创建完成)
 */
PathMeasure(Path path, boolean forceClosed)

公共方法

/**
 * 关联一个Path
 */
void setPath(Path path, boolean forceClosed)

/**
 * 是否闭合
 */
boolean isClosed()

/**
 * 获取path长度
 */
float getLength()

/**
 * 跳转到下一个轮廓
 */
boolean nextContour()

/**
 * 截取片段
 */
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)

/**
 * 获取指定长度的位置坐标及该店切线值tangle
 */
boolean getPosTan(float distance, float pos[], float tan[])

/**
 * 获取指定长度的位置坐标及该点Matrix(矩阵)
 */
boolean getMatrix(float distance, Matrix matrix, int flags)

PathMeasure的方法也不多,接下来我们逐一讲解。

1.构造函数

  • 无参构造函

       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可以闭合的话)

注意:1.不论 forceClosed 设置为何种状态(true 或者 false), 都不会影响原有Path的状态,即 Path 与 PathMeasure 关联之后,之前的的 Path 不会有任何改变。2.forceClosed 的设置状态可能会影响测量结果,如果 Path 未闭合但在与 PathMeasure 关联的时候设置 forceClosed 为 true 时,测量结果可能会比 Path 实际长度稍长一点,获取到到是该 Path 闭合时的状态。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Path path1 = new Path();
        path1.moveTo(195,195);
        path1.lineTo(405,195);
        path1.lineTo(405,405);
        path1.lineTo(195,405);
        canvas.drawPath(path1,p1);

        Path path2 = new Path();
        path2.moveTo(200,200);
        path2.lineTo(400,200);
        path2.lineTo(400,400);
        path2.lineTo(200,400);

        PathMeasure pathMeasure = new PathMeasure();
        pathMeasure.setPath(path2,false);
        Path path3 = new Path();
        pathMeasure.getSegment(0,pathMeasure.getLength(),path3,true);
        canvas.drawPath(path3,p2);

    }

绿色线是模拟关联的path画出来的  红色的线pathMeasure测量路径

2.getSegment

用于获取path的一个片段

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

返回值boolean 判断是否截取成功 true表示截取成功,结果存入dst中,false截取失败,不会改变dst内容

startD: 开始截取位置距离Path起点的长度

stopD: 结束截取位置距离Path起点的长度

dst:截取的Path会添加到dst中

startWithMoveTo: 起始点是否使用moveTo 用于保证截取的Path第一个点位置不变

注意:1.如果 startD、stopD 的数值不在取值范围 [0, getLength] 内,或者 startD == stopD 则返回值为 false,不会改变 dst 内容。2.如果在安卓4.4或者之前的版本,在默认开启硬件加速的情况下,更改 dst 的内容后可能绘制会出现问题,请关闭硬件加速或者给 dst 添加一个单个操作,例如: dst.rLineTo(0, 0)

public class MyPathMeasure extends View {

    private final Paint p2;
    private final PathMeasure pathMeasure;
    private final Path path2;
    private final Path path;

    public MyPathMeasure(Context context) {
        super(context);
        p2 = new Paint();
        p2.setStyle(Paint.Style.STROKE);
        p2.setStrokeWidth(10);
        p2.setColor(Color.RED);
        path2 = new Path();
        path2.addCircle(300, 300, 105, Path.Direction.CW);
        pathMeasure = new PathMeasure();
        pathMeasure.setPath(path2, true);

        path = new Path();
        ss();
    }

    private boolean flag = false;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (flag) {
           path.reset();
            pathMeasure.getSegment(0,pathMeasure.getLength()*dx,path,true);
            canvas.drawPath(path,p2);
        }
    }
    float dx;
    @SuppressLint("WrongConstant")
    private void ss() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setDuration(3000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (float) animation.getAnimatedValue();
                flag = true;
                invalidate();
            }
        });
        animator.start();
    }
}

​​​​​​​

3.nextContour

我们知道 Path 可以由多条曲线构成,但不论是 getLength , getgetSegment 或者是其它方法,都只会在其中第一条线段上运行,而这个 nextContour 就是用于跳转到下一条曲线到方法,如果跳转成功,则返回 true 如果跳转失败,则返回 false。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Path path1 = new Path();
        path1.addCircle(300,300,100,Path.Direction.CW);
        canvas.drawPath(path1,p1);
        Path path4 = new Path();
        path4.addRect(100,100,300,300,Path.Direction.CW);
        canvas.drawPath(path4,p1);

        Path path2 = new Path();
        path2.addCircle(300,300,95,Path.Direction.CW);
        path2.addRect(95,95,305,305,Path.Direction.CW);

        PathMeasure pathMeasure = new PathMeasure();
        pathMeasure.setPath(path2,true);
        Log.i("twy","nextContour之前length="+pathMeasure.getLength());
        pathMeasure.nextContour();
        Log.i("twy","nextContour之后length="+pathMeasure.getLength());
        Path path3 = new Path();
        pathMeasure.getSegment(0,pathMeasure.getLength(),path3,true);
        canvas.drawPath(path3,p2);

    }

注释了pathMeasure.nextContour();

没有注释pathMeasure.nextContour();

4.getPosTan

这个方法是用于得到路径上某一长度的位置以及位置的正切值

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

返回值boolean 判断获取是否成功 true表示成功,数据会存入 pos[] 和tan[]中

distance:距离path起点的长度 取值范围 0<=distance<=getLength

pos: 该点的坐标值 坐标值(x==[0],y==[1])

tan: 该点的正切值 正切值(x==[0],y==[1]) 

通过 tan 得值计算出图片旋转的角度,tan tangent 的缩写,即中学中常见的正切, 其中tan0是邻边边长,tan1是对边边长,而Math atan2 方法是根据正切是数值计算出该角度的大小,得到的单位是弧度,所以上面又将弧度转为了角度。

public class MyPathMeasure extends View {

    private final Paint p2;
    private final PathMeasure pathMeasure;
    private final Path path2;

    public MyPathMeasure(Context context) {
        super(context);
        p2 = new Paint();
        p2.setStyle(Paint.Style.STROKE);
        p2.setStrokeWidth(3);
        p2.setColor(Color.RED);

        path2 = new Path();
        path2.addCircle(300, 300, 105, Path.Direction.CW);

        pathMeasure = new PathMeasure();
        pathMeasure.setPath(path2, true);
        ss();
    }

    private boolean flag = false;
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(flag) {
            float[] pos = new float[2];
            float[] tan = new float[2];
            boolean posTan = pathMeasure.getPosTan(pathMeasure.getLength() * dx, pos, tan);
            if (posTan) {
                float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180f / Math.PI);//Math.atan2 API算切点的角度
                canvas.drawPath(path2,p2);
                canvas.save();
                canvas.rotate(degrees,pos[0],pos[1]);
                canvas.drawLine(pos[0], pos[1], pos[0]+500, pos[1], p2);
                canvas.restore();
            }
        }
    }
    float dx;
    @SuppressLint("WrongConstant")
    private void ss(){
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setDuration(1000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (float) animation.getAnimatedValue();
                flag = true;
                invalidate();
            }
        });
        animator.start();
    }
}

直线和圆相切

5.getMatrix

这个方法是用于得到路径上某一长度的位置以及该位置的正切值的矩阵:

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

返回值 boolean 判断获取是否成功 true表示成功,数据存入matrix中,false失败matrix内不会改变

distance:距离Path起点的长度 取值范围 0<=distance<=getLength

matrix:根据flags封装好的matrix 会根据flags的设置而存入不同的内容

flags:规定哪些内容会存入到 matrix中 可选择 POSITON_MATRIX_FLAG(位置)ANGENT_MATRIX_FLAG(正切)

public class MyPathMeasure extends View {

    private final Paint p2;
    private final PathMeasure pathMeasure;
    private final Path path2;
    private final Bitmap mBitMap;
    private final float[] pos;
    private final float[] tan;
    private final Matrix mMatrix;

    public MyPathMeasure(Context context) {
        super(context);
        p2 = new Paint();
        p2.setStyle(Paint.Style.STROKE);
        p2.setStrokeWidth(3);
        p2.setColor(Color.RED);

        path2 = new Path();
        path2.addCircle(300, 300, 105, Path.Direction.CW);

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;
        mBitMap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, options);

        pos = new float[2];
        tan = new float[2];
        mMatrix = new Matrix();


        pathMeasure = new PathMeasure();
        pathMeasure.setPath(path2, true);
        ss();
    }

    private boolean flag = false;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (flag) {
            canvas.drawPath(path2, p2);
            // 方案一 :自己计算
            // 将tan值通过反正切函数得到对应的弧度,在转化成对应的角度度数
            boolean posTan = pathMeasure.getPosTan(pathMeasure.getLength() * dx, pos, tan);
            if (posTan) {
                float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180f / Math.PI);
                canvas.save();
                canvas.rotate(degrees,pos[0],pos[1]);
                canvas.drawBitmap(mBitMap,pos[0]-mBitMap.getWidth()/2,pos[1]-mBitMap.getHeight(),p2);
                canvas.restore();
            }
            // 方案二 :直接使用API
            /*pathMeasure.getMatrix(pathMeasure.getLength() * dx, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
            mMatrix.preTranslate(-mBitMap.getWidth() / 2, -mBitMap.getHeight());
            canvas.drawBitmap(mBitMap, mMatrix, p2);*/
        }
    }
    float dx;
    @SuppressLint("WrongConstant")
    private void ss() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setDuration(3000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (float) animation.getAnimatedValue();
                flag = true;
                invalidate();
            }
        });
        animator.start();
    }
}

案例

public class MyPathMeasure extends View {

    private final Paint p2;
    private final PathMeasure pathMeasure;
    private final Path path2;
    private final Path path;

    public MyPathMeasure(Context context) {
        super(context);
        p2 = new Paint();
        p2.setStyle(Paint.Style.STROKE);
        p2.setStrokeWidth(3);
        p2.setColor(Color.RED);
        p2.setAntiAlias(true);
        path2 = new Path();
        path2.addCircle(300, 300, 105, Path.Direction.CW);
        path2.lineTo(600, 300);
        path2.addCircle(405,300,195,Path.Direction.CW);
        pathMeasure = new PathMeasure();


        path = new Path();
        ss();
    }

    private boolean flag = false;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (flag) {
            path.reset();
            pathMeasure.setPath(path2, false);
            float dx1 = (float) (dx/0.3)>1?1:(float) (dx/0.3);
            float dx2 = 0;
            float dx3;
            if(dx<=0.3 || dx>0.6){
                dx2 = 0;
            }else if(dx>0.3 && dx<=0.6){
                dx2 = (float) ((dx-0.3)/0.3);
            }

            boolean f = true;
            if(dx<=0.6){
                dx3 = 0;
            }else {
                f = false;
                dx3 = (float) ((dx-0.6)/0.4);
            }
            pathMeasure.getSegment(dx1 * pathMeasure.getLength(), pathMeasure.getLength(), path, true);
            pathMeasure.nextContour();
            pathMeasure.getSegment(pathMeasure.getLength()*dx2, f?pathMeasure.getLength():0, path, true);
            pathMeasure.nextContour();
            pathMeasure.getSegment(pathMeasure.getLength()*dx3, pathMeasure.getLength()*dx3>0?pathMeasure.getLength()*dx3+50:0, path, true);
            canvas.save();
            canvas.rotate(45, 300, 300);
            canvas.drawPath(path, p2);
            canvas.restore();
        }

    }

    float dx;

    @SuppressLint("WrongConstant")
    private void ss() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setDuration(3000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (float) animation.getAnimatedValue();
                flag = true;
                invalidate();
            }
        });
        animator.start();
    }
}

​​​​​​​

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值