路径跟踪 PathMeasure的简单使用

栏目: IT技术 · 发布时间: 4年前

内容简介:平时用path画一些简单的几何图形,呈现的时候也是已经绘制好的图形,想想,如果像动画一样看到它的绘制轨迹,是不是更酷?今天介绍的这个类PathMeasure就是干这个的,知道它的存在还是由于看了启舰写的的自定义控件那本书。好,进入正题。先说说Path,当你设置画笔是描边模式时,你绘制的Path可以看做一条条连续不断的线段首尾相连。当你设置画笔是填充模式时,你绘制一个起点和终点,path是绘制不出来的(除非你绘制3个点)。所以在使用路径跟踪的前提,先把画笔设为描边模式,因为画笔默认的样式是填充模式,这是第一个

平时用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的简单使用

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

路径跟踪 PathMeasure的简单使用

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

public boolean nextContour()

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

路径跟踪 PathMeasure的简单使用

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Linux Command Line

The Linux Command Line

William E. Shotts Jr. / No Starch Press, Incorporated / 2012-1-17 / USD 39.95

You've experienced the shiny, point-and-click surface of your Linux computer-now dive below and explore its depths with the power of the command line. The Linux Command Line takes you from your very ......一起来看看 《The Linux Command Line》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具