当前位置: 移动技术网 > IT编程>移动开发>Android > 路径跟踪 PathMeasure的简单使用

路径跟踪 PathMeasure的简单使用

2020年04月19日  | 移动技术网IT编程  | 我要评论

第一朵杏花教学设计,胶卷儿别跑,1144.us众里寻她千百

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

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

  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当做全封闭路径处理。举个例子:

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

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

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头给首尾相连,如右图。说的已经很详细了,再来张手绘图,应该一目了然了。

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

public boolean nextcontour()

 

  方法讲完,实战写个看看。这里我画的还是从书里看的那个支付完成控件,因为我就是看懂了这个控件,才逐渐能画出五角星,搜索框这样的图标。所以,我还是愿意把启舰写的这个简单却又涉及全面的例子再分享给大家。这里上传的是静态图,实际上是动态的,自己写个demo可以看到绘制过程。

 

 

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);
    }
}

 

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

  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)的原因。

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网