今天周四了,后台还是没时间弄接口,我也只好自high了,上篇博客讲了Path以及贝塞尔曲线的入门知识,今天讲下PathMeasure类,这个类太强大了,所以今天学习下,从类名上看就是对Path进行测量,是path的一个辅助类,现在看看PathMeasure类都有哪些方法给我们调用,以及每个方法都啥意思,你懂这些了,还怕玩不了它,哪直接玩死它,
就这几个方法,其中还有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); } }效果图:
打印的结果:
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的重点,好计算坐标,也许你看到很多博客这么写,
如图:
现在看下我们上面代码执行的效果:
打印结果如下:
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); }效果图:
上面是效果和分析图
现在把getSegment()第四个参数startWithMoveTo参数改为false效果如下:
从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(); } }效果:
第二种不使用动画,就一直改变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); } }效果:
像这种圆形的,其实用这个方法不好,后面会讲一个更好的是宪法方式
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(); } }效果:
如果想速度快点,你只要修改下百分比值,很多杀毒类的app有那个一键加速的效果,用这个就可以实现,
通过这个效果,pos这个参数应该理解很透了,就是path路径上点的坐标,
现在讲下网上一个例子,就是一个三角箭头一直旋转的效果,先把效果贴上来,
这个思路和我们上面小圆一直跑其实是一样的,都是不断的去改变离起始位置的值,只是要旋转和计算平移的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); } }效果:
这离我们上面的效果是不是有点远啊,还好了,就差那么几步而已,要有耐心观看,先看关键的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); }效果:
我们拿第一步实现的效果图和这个图进行比较分析:
好,我们三角头是朝下的,这个只要围绕这个图片的中心点旋转90度就ok了,
哪我们加一行这个代码:
matrix.postRotate(90, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
效果如下:
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); }我们在上面的代码中添加了旋转,这个旋转的角度怎么计算,我提前写好了,现在看下效果:
那么我们现在拿这个效果和第二步效果进行对比分析:
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(); } }效果:
通过这个PathMeasure 的getPosTan()方法的正切值应该理解了吧,我也是研究了好久,如果有哪里不对的地方,希望能一起讨论,下班走人!