• android高级UI之PathMeasure<一>--Path测量基础(nextContour、getPosTan、getMatrix、getSegment)


    前言:

    在上一次https://www.cnblogs.com/webor2006/p/15265808.html完成了对于贝塞尔曲线绘制的学习,今天准备学习UI绘制中经常会用到的跟Path相关的一些知识,也是很重要,但是你不去专门花时间去研究的话其实理解起来也并不轻松,关于掌握了这个技能之后最终你能做出啥UI效果呢?其实很多,这里先提前把要操练的一个效果贴出来,先来感受一下:

     

    其中圆是使用Paint绘制出来的,而那个箭头是一张静态图片,如果在你没有了解PathMeasure它的使用之前,碰到这样的效果是不是有点懵,除了叫UI给出这个效果的一些帧图片出来,然后你用帧动画播出来,是不是没有更好的思路?而学会了PathMeasure它的使用之后,实际利用它还能做出更多效果,再贴一个之后咱们要实现的效果:

    是不是非常常见的一个Loading效果,但是这里同样要求是用纯绘制的方式来实现,而非使用帧动画哟。学好PathMeasure是实现上述效果的一个基础,由于这块我完全陌生,所以打算会花几个篇幅好好的学一下它。

    PathMeasure核心API了解:

    类纵览:

    从这个类名就知道这类的作用---路径测量【其中关于Path的绘制在之前https://www.cnblogs.com/webor2006/p/7341697.html已经学过了】,具体怎么个测量法,往下学习就知道了,先来大致瞅了一下这个类的一些核心方法:

    整体的源码170多行不是很多,但是!!!完全木有用过,所以在正式进行案例的编写之前,先得对这些方法进行一定的了解才行。

    setPath()、getLength():

    API了解:

    其中还可以在构造中进行path的指定:

    其中有一个参数“forceClosed”,强制关闭,这是干嘛用的呢?关于它的理解需要用下面的代码实验进行理解。

    另外getLenth()就不多说了,测量后Path的整个长度的计算方法。

    实践: 

    接下来则用代码执行效果的角度直观的理解一下这些API。

    1、构建一个Path:

    既然要对Path测量,肯定得先有Path才行对吧,所以先来构建一个简单的Path:

    package com.cexo.pathmeasurestudy;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.util.AttributeSet;
    import android.view.View;
    
    import androidx.annotation.Nullable;
    
    /**
     * PathMeasure API了解
     */
    public class MyView extends View {
    
        private Paint defaultPaint;
    
        public MyView(Context context) {
            this(context, null);
        }
    
        public MyView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, -1);
        }
    
        public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            defaultPaint = new Paint();
            defaultPaint.setColor(Color.RED);
            defaultPaint.setStrokeWidth(5);
            defaultPaint.setStyle(Paint.Style.STROKE);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            testForceClosed(canvas);
        }
    
        /**
         * PathMeasure.setPath api了解
         */
        private void testForceClosed(Canvas canvas) {
            Path path = new Path();
    
            path.lineTo(0, 200);
            path.lineTo(200, 200);
            path.lineTo(200, 0);
    
            canvas.drawPath(path, defaultPaint);
    
        }
    }

    此时运行:

    目前坐标点为(0,0)的位置,为了方便观察,咱们将坐标点移到屏幕中间来吧:

    再运行:

    其中可以看到,对于一个正方形,还差一条上边线对吧,这是有意为之的,因为为了之后理解"forceClosed"参数。

    2、构建一个坐标系:

    为了更加清晰的知道坐标点在哪,这里构建一个坐标辅助线:

    此时的效果:

    3、理解forceClosed参数的含义:

    有了Path之后,接下来就可以使用PathMeasure对它进行测量了,如下:

    其中可以发现,如果forceClosed指定为false时,其PathMeasure测量的就是Path的实际长度,因为咱们这个Path就三条线,每条线都是200长,刚好测量出来的Path的总长度为600。

    好,接下来咱们将forceClosed改为true,然后再看一下测量结果:

    变成四条边的长度了,怎么样,此时你能理解forceClosed的含义了么?应该不难了,当它为真时,表示在测量Path时会自动闭合然后再进行测对吧,其中“自动闭合”这个关键字标红了,是因为有细节需要挖掘一下,对于咱们这个例子是很好理解这个闭合的:

    但是!!!要注意了,虽说PathMeasure在测量时主动给加上了上边缺的这条边的长度,可最终绘制还是按咱们Path所指定的来,也就是PathMeasure是不会改变Path的绘制结果的,PathMeasure只是一个工具,这一点需要明白。那如果我们的Path是一条直线而非一个可以闭合的正方形呢?咱们可以进一步再来体会一下“自动闭合”的含义:

    此时的形态为:

    原来一条直线在闭合状态下,其长度也变成了2条边的长度了?其实对于Path中的一条直线,当闭合时是这么走了一圈:

    所以两条边加起来就是400了,关于"forceClosed"参数的作用这就明白了。 

    接下来再从应用效果上来理解一下"forceClosed",对于咱们目前这个路径,如果在测量时指定forceClosed=false情况下,如果有一个图片你想让它按着咱们的Path绘制轨迹来走的话,“应该”就是这样了:

    而当“forceClosed=true”时,其运行轨迹就为:

    其中说的是“应该”,是不是真的如此呢?由于目前还没有能力做出这种效果,待篇末自已来实现这样的效果再来确认这种“应该”。

    nextContour():

    API了解:

    先来看一下官方API的解释:

    你能理解么?很显然是理解不了,啥叫“轮廓”,所以接下来还得以代码的角度来进行理解。其中“Contour”是“轮廓”的意思。

    实践:

    1、多个Path叠加知识了解:

    为了理解这个API,这里需要构建两个Path, 然后会调用Path的图形叠加的API:

    其中第三个参数就有一个模式设置:

    具体有啥作用,直接看下面的构建效果就明白了。

    2、构建Path:

    这里构建两个Path,具体如下:

     

    此时运行的效果如下:

    其中:

    3、通过打印长度来观察nextContour的作用:

    好,接下来咱们则利用PathMeasure对这两个Path进行一个测量,看一下整个Path的长度是多少:

     

    那如果想把里面那个Path也算进来,此时就需要使用它了:

    理解了么?其实也就是默认PathMeasure只会测最外面的Path,对于它里面的,如果你也想测到,就需要调用一下nextContour转到里面的Path进行测量。

    getPosTan():

    API了解:

    先来上官网了解一下它:

    其中这里有一个“切线”的概念,这里其实是初中数学的概念,先来复习理解一下,先度娘一下,这里以圆的切线为例:

    拿图来理解:

    然后经过圆心的线经过切点A:

    然后切线就是指经过切点A且这条经垂直的这条线:

    其中对于咱们这个API不是有两个数组参数么?

    其实它们就是用来获取坐标点的,其中pos参数是获取Path指定distance位置的坐标点,也是就指图中的A点,而tan参数是指圆心点,也就是:

    那,咱们现在代码中运行的效果中,不是圆心,而是缺一条边的正方形呀,其实道理是一样的,下面用代码来实验一下就明白了。

    实践:

    此时运行一下:

    有个细节可能会有疑问,就是对于外面的这个路径我们指定的是获取50长度的位置对吧,也就是这段长度:

    呃,貌似我们在绘制矩形的时候不是按顺时针的么?

    等于这个API是按这么个顺序来进行位置的计算的:

    那如果我们将其改为逆时针,最终效果又会是咋样呢?

    哦,原来是按这么个顺序:

    关于这个小细节,提前了解一下。  

    getMatrix():

    API了解:

    先来看一下官方的说明:

    你会发现貌似跟没解释一样,还是不理解它的作用, 这里理解起来会稍微麻烦一些,还是以一个实际场景为例吧,以下面咱们既将要实践的这个效果为例:

    其中这个箭头想要沿着这个圆的轨迹走,是不是得让图片不断地进行角度变化才行?而对于一个图片在绘制时要转换它的方向有这么一个API:

    而这个matrix的值就可以调用这个API来帮你获得,它直接帮你算了角度并且帮你进行了旋转,而这个API的使用也非常之简单,传你想要进行旋转在path上的位置既可:

    其中第二个参数matrix就是帮我们来获取相关角度信息用的,我们new一个传进来既可,最后一个flags传这俩就成:

    它表示用于指定哪些内容会存入 matrix 中。flags 的值有两个:
    pathMeasure.POSITION_MATRIX_FLAG 表示获取位置信息;
    pathMeasure. TANGENT_MATRIX_FLAG 表示获取切边信息,使得图片按Path 旋转。

    好,关于这块的实践就放在最后的图片按圆轨迹的旋转案例当中,这里就先简单介绍一下其API的使用方式。

    如何自己来实现角度旋转算法:

    目前我们已经知道调用getMatrix() API来获取指定path位置点的旋转角度信息对吧,那如果没有这个api,要你自己来算圆上指定点的角度有没有思路呢?其实有办法,目前咱们不是通过PathMeasure可以获取这俩值了么?

    要想让图片旋转移动,很明显这里有两个动作:旋转+移动,有了pos信息,最起码移动就简单了,直接用matrix中的这个方法既可:

    那接下来就是让图片进行角度旋转了,其实也就是调matrix的这个方法:

    这里需要传三个参数对吧,先来解决容易的这俩:

    所以这俩参数应该是这样传既可:

    现在就剩最后一个难点了,第一个参数需要传旋转的“角度”,其实也不难,因为在之前https://www.cnblogs.com/webor2006/p/7687320.htmlUI的一个案例效果中已经详细的剖析过了,这里回忆一下其计算过程:

     

    当时(x,y)取的是圆里面的坐标,而我们目前是在圆边上的坐标,其实差不多,然后斜率公式为:

    这不就是tangent正切值么?这里百度百科一下啥叫正切值:

    而我们已知正切值了:

    也就是知道了斜率了,接下来再回忆一下当时是怎么通过斜率来计算角度的:

    哦,先根据斜率利用反正切函数来计算出对应的“弧度”,注意不是“角度”哟,其实Math.atan还有另一个较方便的版本:

    所以咱们这里的弧度计算就可以是Math.atan2(tan[1],tan[0])对吧,有了弧度了,再算角度困难么?

     

    而对于这个我们也没必要自己算,因为Math类中已有一个现成转角度的方法:

    至此,要你自己来实现图片围绕一个圆来旋转的效果还难么?这里相当于重新复习了一下之前所学的“旧”知识,这里也再一次说明:“你平常所学的看似不怎么起眼好像没啥用的各种“零碎”的知识,用心学习之后,在未来的某个时刻会发光发亮,任何知识都不是孤独存在的”,所以平常学习中发现某个知识好像跟自己工作并无太大关系时,不要纠结,要学就学透,学扎实,总有一天会派上用场的,坚信这一点。

    getSegment():

    API了解:

    先来看一下官方解释:

    嗯,基本从这API的说明也大致能了解它的用处,好像就是从咱们的Path中来截取指定的一段对吧,是的,用这个API就能实现开篇所展示的这个效果:

    其中绘制的是一个整圆,只是通过这个API,咱们动态的截取了其中的一段才产生了这样的效果,因为很明显它也是按着一个圆的轨迹再跑对吧,所以还是跟Path测量有关。只是目前对于第四个参数"startWithMoveTo"有点不太理解,没关系,下面用代码实例跑一下就明白了。

    实践:

    接下来咱们用代码来看一下这个API的效果,先来绘制一个正方形Path:

    运行看一下:

    好,接下来咱们来截取其中一段,如下:

    再次运行:

    看到了么,确实如咱们的猜想,该API的作用就是截取Path指定的一个区域,其中它是这样截的:

    注意此时dst的起始点还是中间点:

    只是由于这个参数为true,为了保证按原图形的样子,所以此时变成从这块画了:

    那如果将“startWithMoveTo”改为false呢?看一下:

    而由于为false,则不会保证原图形了,直接从起始点连到最后一个点了。貌似对于这个“startWithMoveTo”还是有点模糊,下面再来修改一下代码:

    为true则不会变形,还是保持原样,起始点不会移动。

    有没有发现:

    如果 startWithMoveTo 为 true, 则被截取出来到Path片段保持原状了,而如果 startWithMoveTo 为 false,则会将截取出来的 Path 片段的起始点移动到 dst 的最后一个点,以保证 dst 的连续性。 关于这个参数理解有点生涩,得根据实际你要的效果尝试一下就知道了,对于之后要操练的这个效果:

    很明显是需要startWithMoveTo 为 true的,因为要维持原状。

    总结:

    至此,对于Path的测量基础就已经学得差不多了,是不是有很多平常都不怎么用到的API深挖一下,感觉在自定义View的场景下还是能发挥一些作用的对吧,下次则就以操练PathMeasure为主,实现不同的效果,加强对于PathMeasure使用的掌握。 

  • 相关阅读:
    Redis 安装
    Git的安装和使用
    HTML5 本地存储+layer弹层组件制作记事本
    PHP 微信公众号开发
    PHP 微信公众号开发
    Electron 安装与使用
    HTML5 桌面消息提醒
    Composer安装和使用
    玄 学
    区间内的真素数
  • 原文地址:https://www.cnblogs.com/webor2006/p/15488224.html
Copyright © 2020-2023  润新知