(十五)PathMeasure

来源:互联网 发布:苹果mac自有办公软件 编辑:程序博客网 时间:2024/06/05 10:33

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、PathMeasure 基础

从名字就可以看出, PathMeasure 是一个用来测量 Path 的类。

1.构造方法 PathMeasure()

创建一个空的 PathMeasure。

2.构造方法 PathMeasure(Path path, boolean forceClosed)

创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。

这边需要强调的是参数 forceClosed : 当 Path 有调用 close() 方法的时候,forceClosed 为 true 或 false 没有区别。当 Path 没有调用 close() 方法的,且 forceClosed 为 true 的时候,调用 PathMeasure 的 getLength() 方法,长度会增加关闭区域的长度。

有调用 close() 方法:

        Path path = new Path();        path.lineTo(0,200);        path.lineTo(200,200);        path.lineTo(200,0);        path.close();        PathMeasure measure1 = new PathMeasure(path,false);        PathMeasure measure2 = new PathMeasure(path,true);        Log.e(TAG, "forceClosed=false length = "+measure1.getLength());        Log.e(TAG, "forceClosed=true length = "+measure2.getLength());        canvas.drawPath(path,mDeafultPaint);

效果:
这里写图片描述

日志输出:

E/MyView: forceClosed=false length = 800.0E/MyView: forceClosed=true length = 800.0

没有调用 close() 方法:

        Path path = new Path();        path.lineTo(0,200);        path.lineTo(200,200);        path.lineTo(200,0);        PathMeasure measure1 = new PathMeasure(path,false);        PathMeasure measure2 = new PathMeasure(path,true);        Log.e(TAG, "forceClosed=false length = "+measure1.getLength());        Log.e(TAG, "forceClosed=true length = "+measure2.getLength());        canvas.drawPath(path,mDeafultPaint);

效果:
这里写图片描述

日志输出:

E/MyView: forceClosed=false length = 600.0E/MyView: forceClosed=true length = 800.0

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

截取片段。

startD : 截取片段的起始长度(取值范围在 0 ~ Path 的长度之间)
stopD : 截取片段的终止长度(取值范围在 0 ~ Path 的长度之间),切必须大于 startD,否则取不到
dst : 截取后片段会赋值给 dst
startWithMoveTo : 起始点是否使用 moveTo 方法(一般情况为 true)
如果 startWithMoveTo 设置为 false的话,dst 中保存的 Path 是就会不断被添加的,而不是被覆盖,新增的片段会从上一次 Path 终点开始计算,这样所有截取的 Path 就会连续起来。

这边比较难理解的是第四个参数 startWithMoveTo。为了方便看效果,这里先建立了一个坐标系,并把中心移到屏幕中心。

startWithMoveTo 为 true:

        // 平移坐标系        canvas.translate(mViewWidth/2,mViewHeight/2);        // 画坐标线        canvas.drawLine(-canvas.getWidth(),0,canvas.getWidth(),0,mPaint);        canvas.drawLine(0,-canvas.getHeight(),0,canvas.getHeight(),mPaint);        Path path = new Path();        // 创建Path并添加了一个矩形        path.addRect(-200, -200, 200, 200, Path.Direction.CW);        Path dst = new Path();        // 将 Path 与 PathMeasure 关联        PathMeasure measure = new PathMeasure(path, false);        // 截取一部分存入dst中,并使用 moveTo 保持截取得到的 Path 第一个点的位置不变        measure.getSegment(200, 600, dst, true);        canvas.drawPath(path,mPaint);        canvas.drawPath(dst, mDeafultPaint);

效果:
这里写图片描述

为 dst 添加一条初始的线段。

        // 平移坐标系        canvas.translate(mViewWidth/2,mViewHeight/2);        // 画坐标线        canvas.drawLine(-canvas.getWidth(),0,canvas.getWidth(),0,mPaint);        canvas.drawLine(0,-canvas.getHeight(),0,canvas.getHeight(),mPaint);        Path path = new Path();        // 创建Path并添加了一个矩形        path.addRect(-200, -200, 200, 200, Path.Direction.CW);        Path dst = new Path();        dst.lineTo(100, 100);        // 将 Path 与 PathMeasure 关联        PathMeasure measure = new PathMeasure(path, false);        // 截取一部分存入dst中,并使用 moveTo 保持截取得到的 Path 第一个点的位置不变        measure.getSegment(200, 600, dst, true);        canvas.drawPath(path,mPaint);        // 绘制 dst        canvas.drawPath(dst, mDeafultPaint);

效果:
这里写图片描述

startWithMoveTo 为 false:

        // 平移坐标系        canvas.translate(mViewWidth/2,mViewHeight/2);        // 画坐标线        canvas.drawLine(-canvas.getWidth(),0,canvas.getWidth(),0,mPaint);        canvas.drawLine(0,-canvas.getHeight(),0,canvas.getHeight(),mPaint);        Path path = new Path();        // 创建Path并添加了一个矩形        path.addRect(-200, -200, 200, 200, Path.Direction.CW);        Path dst = new Path();        // 将 Path 与 PathMeasure 关联        PathMeasure measure = new PathMeasure(path, false);        // 截取一部分存入dst中,不使用 moveTo        measure.getSegment(200, 600, dst, false);        canvas.drawPath(path,mPaint);        canvas.drawPath(dst, mDeafultPaint);

效果:
这里写图片描述
startWithMoveTo 为 false,截取的 Path 没有被 moveTo 到起始位置,所以默认 moveTo 到(0, 0)所以导致了绘制的红线从原点开始

为 dst 添加一条初始的线段。

        // 平移坐标系        canvas.translate(mViewWidth/2,mViewHeight/2);        // 画坐标线        canvas.drawLine(-canvas.getWidth(),0,canvas.getWidth(),0,mPaint);        canvas.drawLine(0,-canvas.getHeight(),0,canvas.getHeight(),mPaint);        Path path = new Path();        // 创建Path并添加了一个矩形        path.addRect(-200, -200, 200, 200, Path.Direction.CW);        Path dst = new Path();        dst.lineTo(100, 100);        // 将 Path 与 PathMeasure 关联        PathMeasure measure = new PathMeasure(path, false);        // 截取一部分存入dst中,并使用 moveTo 保持截取得到的 Path 第一个点的位置不变        measure.getSegment(200, 600, dst, false);        canvas.drawPath(path,mPaint);        // 绘制 dst        canvas.drawPath(dst, mDeafultPaint);

效果:
这里写图片描述

4.nextContour()

获取下一条曲线轮廓,当 Path 里面有多条轮廓的时候,会依次取获取。

        // 平移坐标系        canvas.translate(mViewWidth/2,mViewHeight/2);        // 画坐标线        canvas.drawLine(-canvas.getWidth(),0,canvas.getWidth(),0,mPaint);        canvas.drawLine(0,-canvas.getHeight(),0,canvas.getHeight(),mPaint);        Path path = new Path();        Path path1 = new Path();        Path path2 = new Path();        // 添加小矩形        path1.addRect(-100, -100, 100, 100, Path.Direction.CW);        // 添加大矩形        path2.addRect(-200, -200, 200, 200, Path.Direction.CW);        path.op(path1,path2, Path.Op.XOR);        canvas.drawPath(path,mDeafultPaint);        PathMeasure measure = new PathMeasure(path, false);        float len1 = measure.getLength();        // 跳转到下一条路径        measure.nextContour();        float len2 = measure.getLength();        Log.d(TAG,"len1 = "+len1);        Log.d(TAG,"len2 = "+len2);

注:这里必须采用自己设置线条的叠加模式,否则会被覆盖。

效果:
这里写图片描述

日志输出:

D/MyView: len1 = 1600.0D/MyView: len2 = 800.0

5.getPosTan(float distance, float[] pos, float[] tan)

获取指定长度的位置坐标及该点切线值 tangle。

distance: 位置距起始点的距离
pos: 指定位置的坐标,长度为2 (x==pos[0], y==pos[1])
tan: 这个值长度也是2,比较复杂,还是直接看后面效果

效果:
这里写图片描述

public class MyView1 extends View {    private float currentValue = 0;     // 用于纪录当前的位置,取值范围[0,1]映射Path的整个长度    private float[] pos;                // 当前点的实际位置    private float[] tan;                // 当前点的tangent值,用于计算图片所需旋转的角度    private Bitmap mBitmap;             // 箭头图片    private Matrix mMatrix;             // 矩阵,用于对图片进行一些操作    private Paint mDeafultPaint;    private int mViewWidth;    private int mViewHeight;    private Paint mPaint;    public MyView1(Context context) {        super(context);        init(context);    }    private void init(Context context) {        pos = new float[2];        tan = new float[2];        BitmapFactory.Options options = new BitmapFactory.Options();        options.inSampleSize = 8;       // 缩放图片        mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrow, options);        mMatrix = new Matrix();        mDeafultPaint = new Paint();        mDeafultPaint.setColor(Color.RED);        mDeafultPaint.setStrokeWidth(5);        mDeafultPaint.setStyle(Paint.Style.STROKE);        mPaint = new Paint();        mPaint.setColor(Color.DKGRAY);        mPaint.setStrokeWidth(2);        mPaint.setStyle(Paint.Style.STROKE);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mViewWidth = w;        mViewHeight = h;    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.drawColor(Color.WHITE);        // 平移坐标系        canvas.translate(mViewWidth/2,mViewHeight/2);        // 画坐标线        canvas.drawLine(-canvas.getWidth(),0,canvas.getWidth(),0,mPaint);        canvas.drawLine(0,-canvas.getHeight(),0,canvas.getHeight(),mPaint);        Path path = new Path();                                 // 创建 Path        path.addCircle(0, 0, 200, Path.Direction.CW);           // 添加一个圆形        PathMeasure measure = new PathMeasure(path, false);     // 创建 PathMeasure        currentValue += 0.005;                                  // 计算当前的位置在总长度上的比例[0,1]        if (currentValue >= 1) {            currentValue = 0;        }        // 获取当前位置的坐标以及趋势        measure.getPosTan(measure.getLength() * currentValue, pos, tan);        // 重置Matrix        mMatrix.reset();        // 计算图片旋转角度        float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);        // 旋转图片        mMatrix.postRotate(degrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);        // 将图片绘制中心调整到与当前点重合        mMatrix.postTranslate(pos[0] - mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight() / 2);        canvas.drawPath(path, mDeafultPaint);        canvas.drawBitmap(mBitmap, mMatrix, mDeafultPaint);        invalidate();    }}

核心代码:

        // 获取当前位置的坐标以及趋势        measure.getPosTan(measure.getLength() * currentValue, pos, tan);        // 重置Matrix        mMatrix.reset();        // 计算图片旋转角度        float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);        // 旋转图片        mMatrix.postRotate(degrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);        // 将图片绘制中心调整到与当前点重合        mMatrix.postTranslate(pos[0] - mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight() / 2);

对与移动图片不仅进行了平移,也进行了旋转。平移这边是采用后乘平移 postTranslate,这个平移是相对于坐标点(0, 0)。这边比较难懂的是旋转的角度,

以(0, 0)为圆心,新建半径为 1 的圆。直线旋转后与圆相交的点的 x, y 坐标即 getPosTan 方法中 tan 的两个值。
这里写图片描述
利用反 tan 函数,可以登出旋转角度为:Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI。

6.getMatrix(float distance, Matrix matrix, int flags)

获取指定长度的位置坐标及该点 Matrix (矩阵)

distance: 位置距起始点的距离
matrix: 指定位置的矩阵
flags : 标志,flags 有两个选项值,表示矩阵关联的关系

    public static final int POSITION_MATRIX_FLAG = 0x01;    // must match flags in SkPathMeasure.h //位置信息     public static final int TANGENT_MATRIX_FLAG  = 0x02;    // must match flags in SkPathMeasure.h //切边信息

核心代码:

       // 获取当前位置的坐标以及趋势的矩阵        measure.getMatrix(measure.getLength() * currentValue, mMatrix,                PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);        // 将图片绘制中心调整到与当前点重合(注意:此处是前乘pre)        mMatrix.preTranslate(-mBitmap.getWidth() / 2, -mBitmap.getHeight() / 2);

把 getPosTan 的核心代码替换成这个也能实现一样的功能。
注:这边采用平移前乘矩阵,且由于 getMatrix 标志用到了位置信息,所以平移的时候相对位置点是路径上选中的那个点。

三、getSegment 实现的进度圆圈

这里写图片描述

代码比较简单,就不再具体注释了。

public class LoadingView1 extends View {    private Path mPath;    private Paint mPaint;    private PathMeasure mPathMeasure;    private float mAnimatorValue;    private Path mDst;    private float mLength;    public LoadingView1(Context context) {        this(context,null);    }    public LoadingView1(Context context, AttributeSet attrs) {        this(context, attrs,0);    }    public LoadingView1(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        mPathMeasure = new PathMeasure();        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setStrokeWidth(5);        mPath = new Path();        mPath.addCircle(400, 400, 100, Path.Direction.CW);        mPathMeasure.setPath(mPath, true);        mLength = mPathMeasure.getLength();        mDst = new Path();        final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator) {                mAnimatorValue = (float) valueAnimator.getAnimatedValue();                invalidate();            }        });        valueAnimator.setDuration(2000);        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);        valueAnimator.start();    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mDst.reset();        // 硬件加速的BUG        mDst.lineTo(0,0);        /*float stop = mLength * mAnimatorValue;        mPathMeasure.getSegment(0, stop, mDst, true);*/        float stop = mLength * mAnimatorValue;        float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * mLength));        mPathMeasure.getSegment(start, stop, mDst, true);        canvas.drawPath(mDst, mPaint);    }}

四、水波纹小船改变方向

对上一节的小船效果进行优化,是小船的船头随波浪上下起伏。
这里写图片描述

主要是在绘制小船的时候,运用 getPosTan 或者 getMatrix 对小船进行旋转。

getPosTan 旋转

        mPathMeasure = new PathMeasure(path, false);        //方案一,getPosTan        mPathMeasure.getPosTan(width / 2 + length * (1 - faction), pos, tan);        float degrees = (float) (Math.atan2(tan[1], tan[0])*180f / Math.PI);        mMatrix.postRotate(degrees, mBitmap.getWidth()/2, mBitmap.getHeight() / 2);        mMatrix.postTranslate(pos[0]- mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight());        canvas.drawBitmap(mBitmap, mMatrix, paint);

getMatrix 旋转

        mPathMeasure = new PathMeasure(path, false);        mPathMeasure.getMatrix(width / 2 + length * (1 - faction), mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);        mMatrix.preTranslate(- mBitmap.getWidth() / 2, - mBitmap.getHeight());        canvas.drawBitmap(mBitmap, mMatrix, paint);
原创粉丝点击