96编辑器旗下产品
找图
终身
ID:0
到期时间:永久
退出登录
您的位置:首页 > 百科管理 > 详情

android ui绘制-Android UI | View 的绘制流程详解

原创:找图网 2023-04-22 17:17:57
  • Android UI绘制之View绘制的工作原理

  • 这是AndroidUI绘制流程分析的第二篇文章,主要分析界面中View是如何绘制到界面上的具体过程。

    ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联。

    measure 过程决定了 View 的宽/高, Measure 完成以后,可以通过 getMeasuredWidth 和 getMeasuredHeight 方法来获取 View 测量后的宽/高,在几乎所有的情况下,它等同于View的最终的宽/高,但是特殊情况除外。 Layout 过程决定了 View 的四个顶点的坐标和实际的宽/高,完成以后,可以通过 getTop、getBottom、getLeft 和 getRight 来拿到View的四个顶点的位置,可以通过 getWidth 和 getHeight 方法拿到View的最终宽/高。 Draw 过程决定了 View 的显示,只有 draw 方法完成后 View 的内容才能呈现在屏幕上。

    DecorView 作为顶级 View ,一般情况下,它内部会包含一个竖直方向的 LinearLayout ,在这个 LinearLayout 里面有上下两个部分,上面是标题栏,下面是内容栏。在Activity中,我们通过 setContentView 所设置的布局文件其实就是被加到内容栏中的,而内容栏id为 content 。可以通过下面方法得到 content:ViewGroup content = findViewById(R.) 。通过 (0) 可以得到设置的 view 。 DecorView 其实是一个 FrameLayout , View 层的事件都先经过 DecorView ,然后才传递给我们的 View 。

    MeasureSpec 代表一个32位的int值,高2位代表 SpecMode ,低30位代表 SpecSize , SpecMode 是指测量模式,而 SpecSize 是指在某种测量模式下的规格大小。

    SpecMode 有三类,如下所示:

    UNSPECIFIED

    EXACTLY

    AT_MOST

    LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。

    对于顶级View,即DecorView和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同确定;

    对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同决定;

    MeasureSpec一旦确定,onMeasure就可以确定View的测量宽/高。

    小结一下

    当子 View 的宽高采用 wrap_content 时,不管父容器的模式是精确模式还是最大模式,子 View 的模式总是最大模式+父容器的剩余空间。

    View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即测量、布局、绘制。其中 measure 确定 View 的测量宽/高, layout 确定 view 的最终宽/高和四个顶点的位置,而 draw 则将 View 绘制在屏幕上。

    measure 过程要分情况,如果只是一个原始的 view ,则通过 measure 方法就完成了其测量过程,如果是一个 ViewGroup ,除了完成自己的测量过程外,还会遍历调用所有子元素的 measure 方法,各个子元素再递归去执行这个流程。

    如果是一个原始的 View,那么通过 measure 方法就完成了测量过程,在 measure 方法中会去调用 View 的 onMeasure 方法,View 类里面定义了 onMeasure 方法的默认实现:

    先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源码:

    可以看到, getMinimumWidth 方法获取的是 Drawable 的原始宽度。如果存在原始宽度(即满足 intrinsicWidth > 0),那么直接返回原始宽度即可;如果不存在原始宽度(即不满足 intrinsicWidth > 0),那么就返回 0。

    接着看最重要的 getDefaultSize 方法:

    如果 specMode 为 即未指定模式,那么返回由方法参数传递过来的尺寸作为 View 的测量宽度和高度;

    如果 specMode 不是 即是最大模式或者精确模式,那么返回从 measureSpec 中取出的 specSize 作为 View 测量后的宽度和高度。

    看一下刚才的表格:

    当 specMode 为 EXACTLY 或者 AT_MOST 时,View 的布局参数为 wrap_content 或者 match_parent 时,给 View 的 specSize 都是 parentSize 。这会比建议的最小宽高要大。这是不符合我们的预期的。因为我们给 View 设置 wrap_content 是希望View的大小刚好可以包裹它的内容。

    因此:

    如果是一个 ViewGroup,除了完成自己的 measure 过程以外,还会遍历去调用所有子元素的 measure 方法,各个子元素再递归去执行 measure 过程。

    ViewGroup 并没有重写 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 这几个方法专门用于测量子元素。

    如果是 View 的话,那么在它的 layout 方法中就确定了自身的位置(具体来说是通过 setFrame 方法来设定 View 的四个顶点的位置,即初始化 mLeft , mRight , mTop , mBottom 这四个值), layout 过程就结束了。

    如果是 ViewGroup 的话,那么在它的 layout 方法中只是确定了 ViewGroup 自身的位置,要确定子元素的位置,就需要重写 onLayout 方法;在 onLayout 方法中,会调用子元素的 layout 方法,子元素在它的 layout 方法中确定自己的位置,这样一层一层地传递下去完成整个 View 树的 layout 过程。

    layout 方法的作用是确定 View 本身的位置,即设定 View 的四个顶点的位置,这样就确定了 View 在父容器中的位置;

    onLayout 方法的作用是父容器确定子元素的位置,这个方法在 View 中是空实现,因为 View 没有子元素了,在 ViewGroup 中则进行抽象化,它的子类必须实现这个方法。

    1.绘制背景( (canvas); );

    2.绘制自己( onDraw );

    3.绘制 children( dispatchDraw(canvas) );

    4.绘制装饰( onDrawScrollBars )。

    dispatchDraw 方法的调用是在 onDraw 方法之后,也就是说,总是先绘制自己再绘制子 View 。

    对于 View 类来说, dispatchDraw 方法是空实现的,对于 ViewGroup 类来说, dispatchDraw 方法是有具体实现的。

    通过 dispatchDraw 来传递的。 dispatchDraw 会遍历调用子元素的 draw 方法,如此 draw 事件就一层一层传递了下去。dispatchDraw 在 View 类中是空实现的,在 ViewGroup 类中是真正实现的。

    如果一个 View 不需要绘制任何内容,那么就设置这个标记为 true,系统会进行进一步的优化。

    当创建的自定义控件继承于 ViewGroup 并且不具备绘制功能时,就可以开启这个标记,便于系统进行后续的优化;当明确知道一个 ViewGroup 需要通过 onDraw 绘制内容时,需要关闭这个标记。

    参考:《Android开发艺术探索》

  • Android UI | View 的绘制流程详解

  • 上一篇文章讲解了,从setContentView方法到了解View是如何绘制的: 传送门

    在这篇博客讲述了, 在ViewRootImpl类中performTraversals方法中具体的绘制过程,其中里面就有 performMeasure()、performLayout()、performDraw() 三个方法的调用, 那么要了解View 的测量、布局、绘制,就分别跟这三个方法有关系。

    先来看看performMeasure()方法的调用过程

    先看performMeasure方法,这个方法有两个参数,都是通过getRootMeasureSpec()方法计算得到

    这里有一个关键类MeasureSpec,在这里需要了解下这个类的原理。

    这里要感谢 这位博主 ,他讲述的很清晰,我自己动手测算了,很容易理解。大概就是用一个数字通过高位记录Mode,地位记录size的方式,记录两个数据,都是通过一个掩码做位移运算得来。就是说这个变量(measureSpec)的值可以通过掩码分别得到测量mode 和 测量size。

    继续查看performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);方法:

    这里mView是DecorView对象,那么他调用的实际上是View的measure方法,查询DecorView和FrameLayout都没有measure方法,所以他调用的是View的measure方法

    ()方法如下:

    方法如下:(这个方法里面都很重要)

    我们先看 , measureChildWithMargins 方法,

    getChildMeasureSpec 方法内容如下:

    那么假如我们写的布局根节点是LinearLayout,那么就会在执行到方法里面的onMeasure方法时,就会调用到方法,具体内容如下:

    通过源码可以看到,还是会循环调用子View , 就这样循环递归的测量完最里面的一个view,这个过程中onMeasure方法可能会被多次执行。

    还是从开始

    这里跟measure 调用流程其实一样,DecorView和FrameLayout没有重写layout方法,所以调用的是方法,

    由于当前对象是decorView,所以调用的是方法:

    方法如下:

    继续查看 方法

    在这个方法里面,所有重要的方法都在里面,onDraw、dispatchDraw 等等都在里面,我看了下这个方法里面都挺重要就没删减,也都能看得懂。

    到此View的整个绘制流程就搞清楚了。

    关于子view测量

    1、不管父View是何模式,若子View有确切数值,则子View大小就是其本身大小,且mode是EXACTLY

    2、若子View是match_parent,则模式与父View相同,且大小同父View(若父View是UNSPECIFIED,则子View大小为0)

    3、若子View是wrap_content,则模式是AT_MOST,大小同父View,表示不可超过父View大小(若父View是UNSPECIFIED,则子View大小为0)

    关于绘制流程

    我们自定义的view,基本上只需要重写 onMeasure、onLayout、onDraw即可

  • Android的UI底层是用CPU绘图的还是GPU绘图的呢

  • 安卓有2种绘制模型:

    一.软件绘制模型,这里由CPU主导绘图,视图按照以下2个步骤绘图。

    让视图结构(view hierarchy)失效。

    绘制整个视图结构。

    当应用程序需要更新它的部分UI时,都会调用内容发生改变的View对象的invalidate()方法。无效(invalidation)消息请求会在View对象层次结构中传递,以便计算出需要重绘的屏幕区域(脏区)。然后,Android系统会在View层次结构中绘制所有的跟脏区相交的区域。但是,这种方法有两个缺点:

    1. 绘制了不需要重绘的视图(与脏区域相交的区域)

    2. 掩盖了一些应用的bug(由于会重绘与脏区域相交的区域)

    注意:在View对象的属性发生变化时,如背景色或TextView对象中的文本等,Android系统会自动的调用该View对象的invalidate()方法。

    二.硬件加速绘制模型,这里由GPU主导绘图,视图按照以下3个步骤绘图。

    让视图结构失效。

    记录和更新显示列表(Display List)。

    绘制显示列表。

    这种模式下,Android系统依然会使用invalidate()方法和draw()方法来请求屏幕更新和展现View对象。但Android系统并不是立即执行绘制命令,而是首先把这些View的绘制函数作为绘制指令记录一个显示列表中,然后再读取显示列表中的绘制指令调用OpenGL相关函数完成实际绘制。另一个优化是,Android系统只需要针对由invalidate()方法调用所标记的View对象的脏区进行记录和更新显示列表。没有失效的View对象就简单重用先前显示列表记录的绘制指令来进行简单的重绘工作。

    使用显示列表的目的是,把视图的各种绘制函数翻译成绘制指令保存起来,对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高了视图的显示速度。而对于需要重绘的View,则更新显示列表,然后再调用OpenGL完成绘制。

    在这种绘制模型下,不能依赖一个视图与脏区(dirty region)相交而导致它的draw()方法被自动调用,所以必须要手动调用该视图的invalidate()方法去更新显示列表。如果忘记这么做可能导致视图在改变后不会发生变化。

    最新文章 更多
    最新素材 更多
    公司介绍 网站地图 图片资讯
    Copyright © 2010-2022 山东找图网络科技有限公司  鲁ICP备18007836号-3  邮箱:15653358549@163.com