AndroidAndroid

Path、PathMeasure详解

2019-10-02  本文已影响0人  migill

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

}
上一篇下一篇

猜你喜欢

热点阅读