美文网首页PathAndroid自定义View技术干货
Android Path测量工具:PathMeasure

Android Path测量工具:PathMeasure

作者: 饱醉豚我去年买了个表 | 来源:发表于2017-02-13 17:28 被阅读737次
    • 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
    

    相关文章

      网友评论

        本文标题:Android Path测量工具:PathMeasure

        本文链接:https://www.haomeiwen.com/subject/mcfgbttx.html