android PathMeasure类 详细介绍

今天周四了,后台还是没时间弄接口,我也只好自high了,上篇博客讲了Path以及贝塞尔曲线的入门知识,今天讲下PathMeasure类,这个类太强大了,所以今天学习下,从类名上看就是对Path进行测量,是path的一个辅助类,现在看看PathMeasure类都有哪些方法给我们调用,以及每个方法都啥意思,你懂这些了,还怕玩不了它,哪直接玩死它,

android PathMeasure类 详细介绍_第1张图片

就这几个方法,其中还有2个是构造函数,

无参的构造函数:

如果是无参的,你想想PathMeasure是操作Path的,你Path都没创建出来,操作个毛线,对吧,所以肯定提供了set...什么方法给Path赋值的,我们也看到后面确实提供了setPath()方法,

有2个参数的构造函数,

public PathMeasure(Path path, boolean forceClosed)

参数说明:

path:操作路径的对象

forceClosed:是否关闭,我们知道path类它就有个close()方法,意思是当绘制的路径大于3时,会前后连接成一个图形,比如三角形,四角行等等,这个形参会直接影响到getLength()方法的值,现在通过构造函数和getLength()一起演示下,结合测试和现象你才更明白,ok,写demo开始了

package com.pathmeasuredemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
 * Created by admin on 2016/12/14.
 */
public class PathMeaView1  extends View {
    private static final String TAG = "PathMeaView1";
    private Path path;
    private Paint paint;
    private int width;
    private int height;
    public PathMeaView1(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE);
        path = new Path();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(width/2,height/2);
        path.addRect(new RectF(-100,-100,100,100), Path.Direction.CW);
        PathMeasure pathMeasure = new PathMeasure(path,true);
        Log.e(TAG,"length="+pathMeasure.getLength());
        canvas.drawPath(path,paint);
    }
}
效果图:

android PathMeasure类 详细介绍_第2张图片

打印的结果:

12-15 02:05:57.880 3651-3651/com.pathmeasuredemo E/PathMeaView1: length=800.0

然后你把PathMeasure类的构造函数第二个参数改为false,其实最后打印结果还是800,哪不是没区别么,那不是欺骗老百姓么,不怕被弄死,我只想说,大哥,你path绘制的是一个正方形路径,本来就已经闭合了,所以你false还是true肯定是一样了,在这开始是讲一个特例,我现在绘制一个U行,最后一个线不绘制出来,那么这个时候就可以测试出PathMeasure类构造函数中第二个参数的意义了

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(width/2,height/2);
    path.lineTo(0,100);//没有调用moveTo()就是以(0,0)为起点了,
    path.lineTo(100,100);
    path.lineTo(100,0);
    PathMeasure pathMeasure1 = new PathMeasure(path,false);
    PathMeasure pathMeasure2 = new PathMeasure(path,true);
    Log.e(TAG,"pathMeasure1的长度"+pathMeasure1.getLength());
    Log.e(TAG,"pathMeasure2的长度"+pathMeasure2.getLength());
    canvas.drawPath(path,paint);
}
在这解释下canvas.translate()为啥要平移,因为这个view是除了标题栏和状态栏一下全部的,平移到view的重点,好计算坐标,也许你看到很多博客这么写,

如图:

android PathMeasure类 详细介绍_第3张图片

现在看下我们上面代码执行的效果:

android PathMeasure类 详细介绍_第4张图片

打印结果如下:

12-15 02:31:39.240 26470-26470/com.pathmeasuredemo E/PathMeaView1: pathMeasure1的长度300.0
12-15 02:31:39.240 26470-26470/com.pathmeasuredemo E/PathMeaView1: pathMeasure2的长度400.0

看看区别出来了吧

true:表示起点和终点闭合了,但不是说path图形闭合了

false:表示起点和终点没有闭合

public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 截取path一部分

参数说明:

startD:开始截取位置距离path起点的位置,这不是一个坐标值,是没有负数的

stopD:结束点距离path起点的位置,同理上,这个是小于等于path的总长度(pathmeasure.getLength())

dst:截取的图形成一个path对象,

startWidthMoveTo:表示起点位置是否使用moveTo()

现在通过demo演示:


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(width/2,height/2);
    path.addRect(new RectF(-100,-100,100,100), Path.Direction.CW);
    canvas.drawPath(path,paint);
    PathMeasure pathMeasure= new PathMeasure(path,false);
    Path dst = new Path();
    pathMeasure.getSegment(100,400,dst,true);
    paint.setColor(Color.BLUE);
    canvas.drawPath(dst,paint);
}
效果图:

android PathMeasure类 详细介绍_第5张图片

上面是效果和分析图

现在把getSegment()第四个参数startWithMoveTo参数改为false效果如下:

android PathMeasure类 详细介绍_第6张图片

从2个图对比就可以看的出来true表示不和path的起点连接 false表示和path起点连接,这个说的起点是针对view而言,

利用上面的方法作用,我来实现华为手机关机或者重启一个点在圆的轨迹上做运动的,这里讲2种实现方法

第一种是通过动画完成

开始截取位置距离 Path 起点的长度

package com.pathmeasuredemo;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/14.
 */
public class PathMeaView1  extends View {
    private static final String TAG = "PathMeaView1";
    private Path path;
    private Paint paint;
    private int width;
    private int height;
    private float startD = (float) (2*Math.PI*100/1000);
    private float stopD = (float) (2*Math.PI*100/100);
    public PathMeaView1(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(2);
        path = new Path();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(width/2,height/2);
        path.addCircle(0,0,100, Path.Direction.CW);
        paint.setColor(Color.DKGRAY);
        paint.setStrokeWidth(8);
        canvas.drawPoint(0,0,paint);
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(3);
        canvas.drawPath(path,paint);
        PathMeasure pathMeasure= new PathMeasure(path,false);
        Path dst = new Path();
        pathMeasure.getSegment(startD,stopD,dst,true);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(9);
        canvas.drawPath(dst,paint);
        ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(this,"rotation",0.0f,360);
        objectAnimatorX.setDuration(1000);
        objectAnimatorX.setRepeatCount(ObjectAnimator.INFINITE);
        objectAnimatorX.start();
    }
}
效果:

android PathMeasure类 详细介绍_第7张图片

第二种不使用动画,就一直改变startD和stopD二个值就可以实现:

package com.pathmeasuredemo;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/14.
 */
public class PathMeaView1  extends View {
    private static final String TAG = "PathMeaView1";
    private Path path;
    private Paint paint;
    private int width;
    private int height;
    private float startD = (float) (2*Math.PI*100/1000);
    private float stopD = (float) (2*Math.PI*100/100);
    public PathMeaView1(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(2);
        path = new Path();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(width/2,height/2);
        path.addCircle(0,0,100, Path.Direction.CW);
        paint.setColor(Color.DKGRAY);
        paint.setStrokeWidth(8);
        canvas.drawPoint(0,0,paint);
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(3);
        canvas.drawPath(path,paint);
        PathMeasure pathMeasure= new PathMeasure(path,false);
        Path dst = new Path();
        pathMeasure.getSegment(startD,stopD,dst,true);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(9);
        canvas.drawPath(dst,paint);
        postDelayed(new Runnable() {
            @Override
            public void run() {
               if(startD>=2*100*Math.PI)  {
                   startD = (float) (2*Math.PI*100/1000);
               }
                if(stopD>=2*100*Math.PI)  {
                    stopD = (float) (2*Math.PI*100/100);
                }
                startD+=2;
                stopD+=2;
                postInvalidate();
            }
        },5);
    }
}
效果:

android PathMeasure类 详细介绍_第8张图片

像这种圆形的,其实用这个方法不好,后面会讲一个更好的是宪法方式

public boolean getPosTan(float distance, float pos[], float tan[])  获取指定长度的位置坐标及该点切线值,这个方法才牛逼

参数说明:

distance:距离起点位置的长度

pos:是一个长度为2的数组,如果这个方法执行成功话,那么这pos数组就有值pos[0]是x坐标值,pos[1]是y坐标值

tan:是一个长度为2的数组,如果这个方法执行成功话,那么这tan数组就有值tan[0]是这个切线x坐标值,tan[1]是切线y坐标值

现在利用这个方法实现上面高仿华为手机重启或者关机效果

思路:就是不断的去改变distance的值,然后获取pos坐标值,利用和这个坐标值绘制一个半径为10的效果,就ok

package com.pathmeasuredemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by admin on 2016/12/15.
 */
public class CliclePathView extends View {
    private Paint paint;
    private float[] pos;
    private float[] tan;
    private int mViewWidth,mViewHeight;
    private float percentLength;//path长度的百分比
    private Path path;
    public CliclePathView(Context context) {
        this(context,null);
    }

    public CliclePathView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CliclePathView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();
    }
    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        pos = new float[2];
        tan = new float[2];
        path = new Path();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(mViewWidth / 2, mViewHeight / 2);
        path.reset();
        path.addCircle(0, 0, 200, Path.Direction.CW);
        PathMeasure measure = new PathMeasure(path, false);
        percentLength += 0.005;
        if (percentLength >= 1) {
            percentLength = 0;
        }
        measure.getPosTan(measure.getLength() * percentLength, pos, tan);
        paint.setColor(Color.parseColor("#218868"));
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(4);
        canvas.drawCircle(pos[0],pos[1],8,paint);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.WHITE);
        paint.setStrokeWidth(3);
        canvas.drawPath(path, paint);                                  
        invalidate();
    }
}
效果:

android PathMeasure类 详细介绍_第9张图片

如果想速度快点,你只要修改下百分比值,很多杀毒类的app有那个一键加速的效果,用这个就可以实现,

通过这个效果,pos这个参数应该理解很透了,就是path路径上点的坐标,

现在讲下网上一个例子,就是一个三角箭头一直旋转的效果,先把效果贴上来,

android PathMeasure类 详细介绍_第10张图片

这个思路和我们上面小圆一直跑其实是一样的,都是不断的去改变离起始位置的值,只是要旋转和计算平移的x,y坐标值,

先写基本的架子,也就是把圆和三角箭头绘制出来,这是第一步:

package com.pathmeasuredemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/15.
 */
public class CustomCilcleSpeedView extends View {
    private Paint paint;
    private Path path;
    private Bitmap bitmap;
    private int width,height;
    private float[] pos;
    private float[] tan;
    private Matrix matrix;
    public CustomCilcleSpeedView(Context context) {
        this(context,null);
    }
    public CustomCilcleSpeedView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CustomCilcleSpeedView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }
    private void init(Context context) {
        pos = new float[2];
        tan = new float[2];
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(3);
        path = new Path();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;       // 缩放图片
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrow, options);
        matrix = new Matrix();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(width / 2, height / 2);
        path.reset();
        path.addCircle(0,0,100, Path.Direction.CW);
        paint.setColor(Color.WHITE);
        canvas.drawPath(path, paint);
        PathMeasure pathMeasure = new PathMeasure(path, false);
        pathMeasure.getPosTan(pathMeasure.getLength() * 0, pos, tan);
        canvas.drawBitmap(bitmap,matrix,paint);
        //绘制view的原点坐标
        paint.setColor(Color.RED);
        canvas.drawPoint(0,0,paint);
    }
}
效果:

android PathMeasure类 详细介绍_第11张图片

这离我们上面的效果是不是有点远啊,还好了,就差那么几步而已,要有耐心观看,先看关键的2步代码:

pathMeasure.getPosTan(pathMeasure.getLength() * 0, pos, tan);
canvas.drawBitmap(bitmap,matrix,paint);

第一行代码发现我么并没有在这个path上(也就是这个圆上移动了多少距离),

第二行代码我们绘制这个bitmap的时候并没有去指定点,所以是从原点可以绘制的,从红色圆心点就可以看的出来和三角箭头图片的左上角是重叠的,

第二步通过Matirx矩阵对这个bitmap进行平移

onDraw()中的代码如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(width / 2, height / 2);
    path.reset();
    path.addCircle(0,0,100, Path.Direction.CW);
    paint.setColor(Color.WHITE);
    canvas.drawPath(path, paint);
    PathMeasure pathMeasure = new PathMeasure(path, false);
    pathMeasure.getPosTan(pathMeasure.getLength() * 0, pos, tan);
    matrix.reset();
    matrix.postTranslate(pos[0] - bitmap.getWidth() / 2, pos[1] - bitmap.getHeight() / 2);
    canvas.drawBitmap(bitmap,matrix,paint);
    //绘制view的原点坐标
    paint.setColor(Color.RED);
    canvas.drawPoint(0,0,paint);
}
效果:

android PathMeasure类 详细介绍_第12张图片

我们拿第一步实现的效果图和这个图进行比较分析:

android PathMeasure类 详细介绍_第13张图片

好,我们三角头是朝下的,这个只要围绕这个图片的中心点旋转90度就ok了,

哪我们加一行这个代码:

matrix.postRotate(90, bitmap.getWidth() / 2, bitmap.getHeight() / 2);

效果如下:

android PathMeasure类 详细介绍_第14张图片

ok,但是肯定不能这么干,你想也想的到,这个旋转肯定是跟tan这个返回的数组值有关的,因为我们是一个圆对吧,我现在distance=1/12,哪就相当于我们跑了30度,对不对,

第三步:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(width / 2, height / 2);
    path.reset();
    path.addCircle(0,0,100, Path.Direction.CW);
    paint.setColor(Color.WHITE);
    canvas.drawPath(path, paint);
    PathMeasure pathMeasure = new PathMeasure(path, false);
    pathMeasure.getPosTan(pathMeasure.getLength() * 1/12, pos, tan);
    matrix.reset();
    float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
    matrix.postRotate(degrees, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
    matrix.postTranslate(pos[0] - bitmap.getWidth() / 2, pos[1] - bitmap.getHeight() / 2);
    canvas.drawBitmap(bitmap,matrix,paint);
    //绘制view的原点坐标
    paint.setColor(Color.RED);
    canvas.drawPoint(0,0,paint);
}
我们在上面的代码中添加了旋转,这个旋转的角度怎么计算,我提前写好了,现在看下效果:

android PathMeasure类 详细介绍_第15张图片

那么我们现在拿这个效果和第二步效果进行对比分析:

android PathMeasure类 详细介绍_第16张图片

ok,现在关于bitmap的平移和旋转全部分析出来,代码

现在想让它一直动,很简单,不断的改变distance的值,然后刷新界面就可以做到了,当然了关于每次distance添加的幅度,你自己定好了,全部代码如下:

package com.pathmeasuredemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/15.
 */
public class CustomCilcleSpeedView extends View {
    private Paint paint;
    private Path path;
    private Bitmap bitmap;
    private int width,height;
    private float[] pos;
    private float[] tan;
    private Matrix matrix;
    private float percentLength;//path长度的百分比
    public CustomCilcleSpeedView(Context context) {
        this(context,null);
    }
    public CustomCilcleSpeedView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CustomCilcleSpeedView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }
    private void init(Context context) {
        pos = new float[2];
        tan = new float[2];
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(3);
        path = new Path();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;       // 缩放图片
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrow, options);
        matrix = new Matrix();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(width / 2, height / 2);
        path.reset();
        path.addCircle(0,0,100, Path.Direction.CW);
        paint.setColor(Color.WHITE);
        canvas.drawPath(path, paint);
        PathMeasure pathMeasure = new PathMeasure(path, false);
        percentLength += 0.01;
        if (percentLength >= 1) {
            percentLength = 0;
        }
        pathMeasure.getPosTan(pathMeasure.getLength() * percentLength, pos, tan);
        matrix.reset();
        float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
        matrix.postRotate(degrees, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
        matrix.postTranslate(pos[0] - bitmap.getWidth() / 2, pos[1] - bitmap.getHeight() / 2);
        canvas.drawBitmap(bitmap,matrix,paint);
        //绘制view的原点坐标
        paint.setColor(Color.RED);
        canvas.drawPoint(0,0,paint);
        invalidate();
    }
}
效果:

android PathMeasure类 详细介绍_第17张图片


通过这个PathMeasure 的getPosTan()方法的正切值应该理解了吧,我也是研究了好久,如果有哪里不对的地方,希望能一起讨论,下班走人!


你可能感兴趣的:(自定义控件)