路径跟踪 PathMeasure的简单使用

  平时用path画一些简单的几何图形,呈现的时候也是已经绘制好的图形,想一想,若是像动画同样看到它的绘制轨迹,是否是更酷?今天介绍的这个类PathMeasure就是干这个的,知道它的存在仍是因为看了启舰写的的自定义控件那本书。好,进入正题。canvas

  先说说Path,当你设置画笔是描边模式时,你绘制的Path能够看作一条条接二连三的线段首尾相连。当你设置画笔是填充模式时,你绘制一个起点和终点,path是绘制不出来的(除非你绘制3个点)。因此在使用路径跟踪的前提,先把画笔设为描边模式,由于画笔默认的样式是填充模式,这是第一个注意事项。在使用PathMeasure时,对它的理解越简单通透,你就会知道,用它须要准备哪些变量,变量所有初始完毕,用起来就像照葫芦画瓢同样简单!ide

  PathMeasure和一个你绘制好的完整path绑定,而后,你能够随意设置起点和终点,获得两点之间的path,获得这个path怎么用呢,你是否是会这么想:我有一条线段,分红10等份,我每次更正起点和终点,取0-1,1-2,2-3...这些片断路径来绘制!不用那么麻烦的,你能够直接把起点固定为0,逐渐修正终点,这样你会获得0-1,0-2,0-3...的path。而后交给canvas去绘制就好了,由于canvas在3个10毫秒分别绘制0-1,0-2,0-3,它看起来就是30毫秒内从0-3的完整轨迹。这个过程就相似scrollTo。接下来,看看经常使用的3个方法:动画

   public PathMeasure(Path path, boolean forceClosed) { // The native implementation does not copy the path, prevent it from being GC'd
        mPath = path; native_instance = native_create(path != null ? path.readOnlyNI() : 0, forceClosed); }

 

1.第一个参数就是你要跟踪的path,第二个参数强制关闭,表示是否把你传入的第一个path当作全封闭路径处理。举个例子:spa

假如pathMeasure传的第一个path,是用3条线段绘制的,如左图,每段长为50px,那么pathMeasure构造方法的forceClose为true时,它把你的传入的path当作全封闭路径处理,也就是当初第二张图,这时pathMeasure.genLength()获取的路径就是50*4;若是forceClose设为false,那么它仍是当作path原始的路径,该几条线就几条线,pathMeasure获取的路径长就是50*3。因此,建议构造的pathMeasure,直接设为false,保持原始路径的模样。code

  第二个方法就是最经常使用的截取一段path的方法:blog

public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 

 

  前两个参数一目了然,很少介绍,有意思的是后面两位。第3个dst,就是用来接收截取完以后的desPath,第4个参数的true和false,将直接影响desPath。考虑一下这种状况,假如desPath初始化时就已经添加了一段路径,如(0,0)到(50,50),这是一条斜线。那么以后getSegment方法走完,desPath新增的截取path的起点和终点分别为(100,0)和(100,100),新path怎么个添加法呢?这就是startWithMoveTo()这个方法决定的,它为true:好,绘制完(0,0)到(50,50)以后,系统帮你调一次moveTo(x,y),绘制起点直接挪到截取path的起点上,如左图。若是为false:绘制完(0,0)到(50,50)以后,系统帮你调一次lineTo(x,y),把最开始的path尾巴和我新截取的path头给首尾相连,如右图。说的已经很详细了,再来张手绘图,应该一目了然了。get

  第3个方法很简单的,它的做用就是切换路径轮廓,假如我画了一个圆,我下一条路径在圆里面,可不是在圆周上,怎么办,调它就好了,此时你会发现pathMeasure.getLength()也会重置为你新路径轮廓的长度.animation

public boolean nextContour()

 

  方法讲完,实战写个看看。这里我画的仍是从书里看的那个支付完成控件,由于我就是看懂了这个控件,才逐渐能画出五角星,搜索框这样的图标。因此,我仍是愿意把启舰写的这个简单却又涉及全面的例子再分享给你们。这里上传的是静态图,其实是动态的,本身写个demo能够看到绘制过程。it

 

 

public class AiliPayView extends View { private Paint paint; private Path path,desPath; private PathMeasure pathMeasure; private ValueAnimator valueAnimator; private float curValue; private boolean isNext = false; public AiliPayView(Context context) { super(context); init(); } public AiliPayView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public AiliPayView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init(){ setLayerType(LAYER_TYPE_SOFTWARE, null); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStrokeWidth(8); paint.setStyle(Paint.Style.STROKE); paint.setColor(Color.BLACK); path = new Path(); desPath = new Path(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); path.addCircle(w/2,h/2,w/2-4, Path.Direction.CW); path.moveTo(w/4,h/2); path.lineTo(w/2,h/4*3); path.lineTo(w/4*3,h/4); pathMeasure = new PathMeasure(path, false); valueAnimator = ValueAnimator.ofFloat(0,2); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { curValue = (float) animation.getAnimatedValue(); invalidate(); } }); valueAnimator.setDuration(3000); valueAnimator.start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (curValue < 1){ float stop = pathMeasure.getLength() * curValue; pathMeasure.getSegment(0,stop,desPath,true); }else { if (!isNext){ isNext = true; //这里是临界状态,getlength是圆的总长
                pathMeasure.getSegment(0, pathMeasure.getLength(), desPath, true); //路径轮廓切换
 pathMeasure.nextContour(); }else { //getlength是新轮廓的总长,还有true的设置,由于折线路径在圆里面,因此把绘制起点更换,不然,折线的起点和圆又会连起来
                float stop = pathMeasure.getLength() * (curValue - 1); pathMeasure.getSegment(0, stop, desPath, true); } } canvas.drawPath(desPath,paint); } }

 

代码中有些细节值得注意:io

  1.在获取中画圆时,圆心选取没什么问题,半径的设置可能须要根据长宽不一致,进一步的确认,我这里默认长宽是相等的写法。另外,我设置的圆半径为w/2-4,为何要减4呢?由于画笔的描边宽度我设置的是8,当我不减去画笔宽度的一半时,你能够发现最终的绘制的圆于控件区域相切的4个点,都会显得细一点。你能够想象,画笔的笔尖宽度为8,以它中点扫去,恰好一半的宽度超出的显示区域,因此,半径减去画笔一半的宽度。

  2.从圆路径切换到折线路径,用isNext这个标识。由于咱们的值动画是匀速的,值区域在(0,2),能够保证监听值在<1时,控件还在画圆,那咱们能够认为监听值等于1,就是圆恰好结束吗?实际上是没法保证的,由于不断改变的curValue多是从0.99直接到1.01,它不必定等于1的。 因此咱们在curValue 》=1 的第一时间,咱们绘制完整个圆路径,而后切换路径轮廓,pathMeasure.getLength()就从圆周长 变成了折线的长度。这也是后来的getSegment()的终点值(curValue - 1)的缘由。

相关文章
相关标签/搜索