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();
    }
}

​​​​​​​

 

 

 

 

 

<think> 我们正在讨论的是Android中的PathMeasure类,它是一个用于测量Path路径的工具类。根据引用内容,PathMeasure主要有以下作用: 1. **测量Path路径的长度**:通过`getLength()`方法可以获取整个Path路径的长度。 2. **获取路径片段**:使用`getSegment()`方法可以截取Path的某一段,并将结果存储到另一个Path中。 3. **获取路径上的点坐标和切线值**:`getPosTan()`方法可以在指定距离处获取该点的坐标和切线值(正切值),这对于沿着路径移动对象(如汽车沿着道路移动)非常有用。 4. **获取路径上的点坐标和矩阵**:`getMatrix()`方法可以在指定距离处获取该点的坐标以及该点的Matrix,可以用来对图形进行变换(如旋转、平移等)。 5. **处理多个轮廓**:Path可能由多个轮廓(子路径)组成,`nextContour()`方法可以跳转到下一个轮廓(如果有的话),这样就可以依次处理每个子路径。 6. **关联Path**:通过构造函数或`setPath()`方法可以关联一个Path,然后进行测量操作。 因此,PathMeasure的主要作用是对Path路径进行测量和操作,包括获取长度、截取片段、获取路径上点的位置和方向等,这些功能在动画、绘图和自定义View中非常有用。 下面是一个简单的示例,展示如何使用PathMeasure获取路径上某点的位置和切线值: ```kotlin val path = Path().apply { moveTo(0f, 0f) lineTo(100f, 100f) lineTo(200f, 0f) } val pathMeasure = PathMeasure(path, false) val length = pathMeasure.length // 获取路径总长度 // 获取路径中间点的位置和切线 val midDistance = length / 2 val pos = FloatArray(2) // 存储位置坐标 val tan = FloatArray(2) // 存储切线值(正切值) if (pathMeasure.getPosTan(midDistance, pos, tan)) { // pos[0]和pos[1]是点的x和y坐标 // tan[0]和tan[1]是切线向量(x分量和y分量),可以用于计算角度 val angle = Math.atan2(tan[1].toDouble(), tan[0].toDouble()) * 180 / Math.PI } ``` 在这个示例中,我们创建了一个简单的折线路径,然后使用PathMeasure获取路径中点处的坐标和切线向量,并计算出该点的切线角度。 **注意**:`getPosTan()`方法中的`tan`数组存储的是该点的切线向量,即该点的切线方向。通过这个向量,我们可以计算出该点的切线角度,这在实现物体沿着路径运动并旋转方向时非常有用。 §§相关问题§§ 1. 如何使用PathMeasure实现物体沿着路径运动? 2. 如何利用PathMeasure的getMatrix方法对图形进行变换? 3. PathMeasure的nextContour方法有什么用途?如何使用? </think> PathMeasure 是 Android 图形系统中的核心工具类,主要用于**处理和测量 Path 路径对象**。它的主要作用体现在以下方面: ### 1. 路径测量与分析 - **获取路径总长度**: ```kotlin val path = Path().apply { addCircle(0f, 0f, 100f, Path.Direction.CW) } val measure = PathMeasure(path, false) val length = measure.length // 获取路径总长度 ``` - **检查路径闭合状态**: ```kotlin val isClosed = measure.isClosed // 返回路径是否闭合[^1] ``` ### 2. 路径分割与提取 - **截取路径片段**: ```kotlin val segment = Path() measure.getSegment(0f, length/2, segment, true) // 截取前50%路径 ``` - **处理多轮廓路径**: ```kotlin while (measure.nextContour()) { // 遍历路径中的所有轮廓 val contourLength = measure.length } ``` ### 3. 路径点定位与方向计算 - **获取路径上指定位置的坐标和切线方向**: ```kotlin val pos = FloatArray(2) // 存储坐标[x,y] val tan = FloatArray(2) // 存储切线向量[x,y] measure.getPosTan(length/2, pos, tan) // 获取中点位置和方向[^2] ``` - **获取路径点的变换矩阵**: ```kotlin val matrix = Matrix() measure.getMatrix(length/2, matrix, PathMeasure.POSITION_MATRIX_FLAG) ``` ### 4. 实际应用场景 1. **动画路径追踪**: ```kotlin // 让View沿着路径移动 val animator = ObjectAnimator.ofFloat(view, "percentage", 0f, 1f).apply { interpolator = LinearInterpolator() addUpdateListener { anim -> val distance = anim.animatedValue as Float * measure.length measure.getPosTan(distance, pos, tan) view.x = pos[0] - view.width/2 view.y = pos[1] - view.height/2 } } ``` 2. **路径切割动画**: ```kotlin val progressPath = Path() measure.getSegment(0f, progress * measure.length, progressPath, true) canvas.drawPath(progressPath, paint) // 绘制部分路径 ``` 3. **自定义图形操作**: - 计算路径上等间距点 - 生成沿路径的文本效果 - 实现路径擦除动画 ### 使用注意事项 1. **性能优化**: ```kotlin // 避免在draw()中频繁创建PathMeasure val measure = PathMeasure().apply { setPath(myPath, false) // 重用实例 } ``` 2. **闭合路径处理**: ```kotlin PathMeasure(path, true) // forceClosed=true强制闭合路径计算 ``` 3. **路径方向**: 切线向量`tan[0]`和`tan[1]`表示方向向量,可用于计算旋转角度: ```kotlin val angle = Math.atan2(tan[1].toDouble(), tan[0].toDouble()) * 180 / Math.PI ``` PathMeasure 通过精确的路径测量能力,为复杂路径操作提供了数学基础,是实现高级绘图效果的核心工具[^1][^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值