(十五)PathMeasure

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

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

一、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.0
E/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.0
E/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.0
D/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);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值