美文网首页AndroidAndroid
Path、PathMeasure详解

Path、PathMeasure详解

作者: migill | 来源:发表于2019-10-02 17:04 被阅读0次

    1、概念

    路径,可用于绘制直线,曲线构成几何路径,还可用于根据路径绘制文字

    2、常用API

    常用API如移动,连线,闭合,添加图形等

    void moveTo(float x, float y): 移动
    void lineTo(float x, float y):连线
    rLineTo(float dx, float dy):表示相对位置,相对上一个点
    void close():设置曲线是否闭合
    void addArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle):添加弧形,startAngle开始角度,sweepAngle顺时针旋转角度
    void addRect(float left, float top, float right, float bottom, Direction dir):添加矩形,Path.Direction.CW表示顺时针方向绘制,CCW表示逆时针方向
    void addCircle(float x, float y, float radius, Direction dir) :添加圆
    void addOval(float left, float top, float right, float bottom, Direction dir):添加椭圆
    void addPath(Path src):添加一个路径
    void arcTo(float left, float top, float right, float bottom, float startAngle,
    float sweepAngle, boolean forceMoveTo):forceMoveTo,true,绘制时移动起点,false,绘制时连接最后一个点与圆弧起点
    void quadTo(float x1, float y1, float x2, float y2):绘制二阶贝塞尔曲线,控制点 (x1,y1),结束点(x2,y2)
    void rQuadTo(float dx1, float dy1, float dx2, float dy2):相对于起始点的相对控制点和结束点,控制点 (dx1,dy1),结束点(dx2,dy2)
    void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3):绘制三阶贝塞尔曲线,控制点(x1,y1) 和 (x2,y2), 结束点 (x3,y3)
    void rCubicTo(float x1, float y1, float x2, float y2,float x3, float y3):绘制三阶贝塞尔曲线,相对于起始点的相对位置坐标

    Path mPath = new Path();
    mPath.moveTo(100, 70); //移动
    mPath.lineTo(140, 800);//连线
    mPath.rLineTo(40, 730);//相对上一个点的位置连线
    mPath.close();//设置曲线是否闭合
    mPath.addArc(200, 200, 400, 400, -225, 225);//添加弧形
    
    //添加子图形addXXX
    //添加弧形
    mPath.addArc(200, 200, 400, 400, -225, 225);
    //Path.Direction.CW表示顺时针方向绘制,CCW表示逆时针方向
    mPath.addRect(500, 500, 900, 900, Path.Direction.CW);
    //添加一个圆
    mPath.addCircle(700,700, 200, Path.Direction.CW);
    //添加一个椭圆
    mPath.addOval(0,0,500,300, Path.Direction.CCW);
    
    //forceMoveTo,true,绘制时移动起点,false,绘制时连接最后一个点与圆弧起点
    mPath.arcTo(400, 200, 600, 400, 0, 270, false);
    
    //添加一个路径
    Path newPath = new Path();
    newPath.moveTo(100, 1000);
    newPath.lineTo(600, 1300);
    newPath.lineTo(400, 1700);
    mPath.addPath(newPath);
    
    //添加圆角矩形, CW顺时针,CCW逆时针
    RectF rectF5 = new RectF(200, 800, 700, 1200);
    mPath.addRoundRect(rectF5, 20, 20, Path.Direction.CCW);
    
    //画二阶贝塞尔曲线
    mPath.moveTo(300, 500);
    mPath.quadTo(500, 100, 800, 500);
    //参数表示相对位置,等同于上面一行代码
    mPath.rQuadTo(200, -400, 500, 0);
    
    //画三阶贝塞尔曲线
    mPath.moveTo(300, 500);
    mPath.cubicTo(500, 100, 600, 1200, 800, 500);
    //参数表示相对位置,等同于上面一行代码
    mPath.rCubicTo(200, -400, 300, 700, 500, 0);
    
    canvas.drawPath(mPath, mPaint);
    

    3、PathMeasure

    常用API:
    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):获取指定长度位置矩阵

    1、关联path,获取关联Path的长度
    pathMeasure需要关联一个创建好的path,如果Path进行了调整,需要重新调用setPath方法进行关联。

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mLinePaint);
            canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mLinePaint);
            canvas.translate(getWidth() / 2, getHeight() / 2);
    
            Path path = new Path();
            path.lineTo(0,200);
            path.lineTo(200,200);
            path.lineTo(200,0);
             /**
             * pathMeasure需要关联一个创建好的path, forceClosed会影响Path的测量结果
             */
            PathMeasure pathMeasure = new PathMeasure();
            pathMeasure.setPath(path, true);
            Log.e("TAG", "onDraw:forceClosed=true "+ pathMeasure.getLength());
    
            PathMeasure pathMeasure2 = new PathMeasure();
            pathMeasure2.setPath(path, false);
            Log.e("TAG", "onDraw:forceClosed=false "+ pathMeasure2.getLength());
    
            PathMeasure pathMeasure1 = new PathMeasure(path, false);
            Log.e("TAG", "onDraw:PathMeasure(path, false) "+ pathMeasure1.getLength());
            path.lineTo(200, -200);
            Log.e("TAG", "onDraw:PathMeasure(path, false) "+ pathMeasure1.getLength());
            //如果Path进行了调整,需要重新调用setPath方法进行关联
            pathMeasure1.setPath(path, false);
            Log.e("TAG", "onDraw:PathMeasure(path, false) "+ pathMeasure1.getLength());
            canvas.drawPath(path, mPaint);
        }
    

    XXX: onDraw:forceClosed=true 800.0
    XXX: onDraw:forceClosed=false 600.0
    XXX: onDraw:PathMeasure(path, false) 600.0
    XXX: onDraw:PathMeasure(path, false) 600.0
    XXX: onDraw:PathMeasure(path, false) 800.0

    2、获取片段

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

    这个API用于截取整个Path的片段,通过参数startD和stopD来控制截取的长度,并将截取的Path保存到dst中,最后一个参数startWithMoveTo表示起始点是否使用moveTo方法,通常为True,保证每次截取的Path片段都是正常的、完整的。

    如果startWithMoveTo设置为false,通常是和dst一起使用,因为dst中保存的Path是被不断添加的,而不是每次被覆盖,设置为false,则新增的片段会从上一次Path终点开始计算,这样可以保存截取的Path片段数组连续起来

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mLinePaint);
            canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mLinePaint);
            canvas.translate(getWidth() / 2, getHeight() / 2);
            Path path = new Path();
            path.addRect(-200,-200, 200,200, Path.Direction.CW);
    
            Path dst = new Path();
            dst.lineTo(-300,-300);//添加一条直线
    
            PathMeasure pathMeasure = new PathMeasure(path, false);
            //截取一部分存入dst中,并且使用moveTo保持截取得到的Path第一个点位置不变。
            pathMeasure.getSegment(0, 1000, dst, true);
            canvas.drawPath(path, mPaint);
        }
    

    pathMeasure.getSegment(0, 1000, dst, true)的效果



    pathMeasure.getSegment(0, 1000, dst, false)的效果


    3、跳转到下一个轮廓

    boolean nextContour()

    nextContour()方法用的比较少,比较大部分情况下都只会有一个Path而不是多个,毕竟这样会增加Path的复杂度,但是如果真有一个Path,包含了多个Path,那么通过nextContour这个方法,就可以进行切换,同时,默认的API,例如getLength,获取的也是当前的这段Path所对应的长度,而不是所有的Path的长度,同时,nextContour获取Path的顺序,与Path的添加顺序是相同的。

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.translate(getWidth() / 2, getHeight() / 2);
            Path path = new Path();
            path.addRect(-100,-100,100,100, Path.Direction.CW);//添加一个矩形
            path.addOval(-200,-200,200,200, Path.Direction.CW);//添加一个椭圆
            canvas.drawPath(path, mPaint);
            PathMeasure pathMeasure = new PathMeasure(path, false);
            Log.e("TAG", "onDraw:forceClosed=false "+ pathMeasure.getLength());
            //跳转到下一条曲线
            pathMeasure.nextContour();
            Log.e("TAG", "onDraw:forceClosed=false "+ pathMeasure.getLength());
            canvas.drawPath(mPath, mPaint);
        }
    

    打印结果:
    2019-10-03 16:04:54.932 9902-9902/cXXX E/TAG: onDraw:forceClosed=false 800.0
    2019-10-03 16:04:54.932 9902-9902/XXX E/TAG: onDraw:forceClosed=false 1256.1292

    4、获取指定长度位置坐标及该点切线值

    boolean getPosTan (float distance, float[] pos, float[] tan)
    distance:指定的长度
    pos:指定长度的位置坐标
    tan:当前点在曲线上的方向与X轴之间的夹角在单位圆中的对边与邻边。tan[0]是临边长度;tan[1]是对边长度

    举例:图片在圆环上不断的进行旋转

    public class PathMeasurePosTan extends View {
    
        private Paint mPaint = new Paint();
        private Paint mLinePaint = new Paint(); //坐标系
        private Bitmap mBitmap;
        private Matrix mMatrix = new Matrix();
        private float[] pos = new float[2];
        private float[] tan = new float[2];
        private Path mPath = new Path();
        private float mFloat;
    
    
        public PathMeasurePosTan(Context context) {
            super(context);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(Color.BLACK);
            mPaint.setStrokeWidth(4);
    
            mLinePaint.setStyle(Paint.Style.STROKE);
            mLinePaint.setColor(Color.RED);
            mLinePaint.setStrokeWidth(6);
    
            //缩小图片
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 4;
            mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.arrow, options);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mLinePaint);
            canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mLinePaint);
    
            canvas.translate(getWidth() / 2, getHeight() / 2);
    
            mPath.reset();
            mPath.addCircle(0, 0, 200, Path.Direction.CW);
            canvas.drawPath(mPath, mPaint);
    
            mFloat += 0.01;
            if (mFloat >= 1) {
                mFloat = 0;
            }
    
            PathMeasure pathMeasure = new PathMeasure(mPath, false);
            pathMeasure.getPosTan(pathMeasure.getLength() * mFloat, pos, tan);
    
            //计算出当前的切线与x轴夹角的度数
            double degrees = Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI;
    
            mMatrix.reset();
            //进行角度旋转
            mMatrix.postRotate((float) degrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
            //将图片的绘制点中心与当前点重合
            mMatrix.postTranslate(pos[0] - mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight() / 2);
            canvas.drawBitmap(mBitmap, mMatrix, mPaint);
    
            invalidate();
        }
    
    }
    

    5、Matrix 获取指定长度位置矩阵

    boolean getMatrix(float distance, Matrix matrix, int flags)
    distance:距离path起点的长度
    matrix:将信息存放在matrix中
    flags:指定存放信息的类型,POSITION_MATRIX_FLAG表示位置信息,TANGENT_MATRIX_FLAG表示当前点在曲线上的方向,对应getPosTan中的tan

    例子:图片在圆环上不断的进行旋转

    public class PathMeasureMatrix extends View {
    
        private Paint mPaint = new Paint();
        private Paint mLinePaint = new Paint(); //坐标系
        private Bitmap mBitmap;
        private Matrix mMatrix = new Matrix();
        private float[] pos = new float[2];
        private float[] tan = new float[2];
        private Path mPath = new Path();
        private float mFloat;
    
        public PathMeasureMatrix(Context context) {
            super(context);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(Color.BLACK);
            mPaint.setStrokeWidth(4);
    
            mLinePaint.setStyle(Paint.Style.STROKE);
            mLinePaint.setColor(Color.RED);
            mLinePaint.setStrokeWidth(6);
    
            //缩小图片
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 4;
            mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.arrow, options);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mLinePaint);
            canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mLinePaint);
    
            canvas.translate(getWidth() / 2, getHeight() / 2);
    
            mPath.reset();
            mPath.addCircle(0,0,200, Path.Direction.CW);
            canvas.drawPath(mPath, mPaint);
    
            mFloat += 0.01;
            if (mFloat >= 1) {
                mFloat = 0;
            }
    
            PathMeasure pathMeasure = new PathMeasure(mPath, false);
            //将pos信息和tan信息保存在mMatrix中
            pathMeasure.getMatrix(pathMeasure.getLength() * mFloat, mMatrix, PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);
            //将图片的旋转坐标调整到图片中心位置
            mMatrix.preTranslate(-mBitmap.getWidth() / 2, -mBitmap.getHeight() / 2);
    
            canvas.drawBitmap(mBitmap,mMatrix, mPaint);
    
            invalidate();
        }
    
    }
    

    相关文章

      网友评论

        本文标题:Path、PathMeasure详解

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