美文网首页程序员
Android的View绘制流程

Android的View绘制流程

作者: 奔跑之咸鱼 | 来源:发表于2018-04-29 14:58 被阅读0次

    看见好多文章都写View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,从刚开始迈步的时候就摔了一大跤,为什么呢?因为我在View的源码中就找不到ViewRoot这个类,只有一个ViewRootHandler类,我以为我找错了,我觉得ViewRoot应该是单独的类,于是尝试着声明了一个ViewRoot类型的变量,企图直接找到他,然而根本没有这东西,我猜会不会在ViewGroup呢?毕竟都带个View嘛,然而还是没有找到...我就滚回去看了一下ViewRootHandler,发现里面有一行requestLayout()!顾名思义,请求布局,那我就切(按着ctrl,用鼠标左键点他)进去

    requestLayout的实现

    checkThread是确认发起请求的布局是否处在主线程,这里有一个scheduleTraversals(),和performTraversals()有点像,都是遍历,那就切进去看看吧,似乎没有我们要找的东西,不过你看这里有一个getLooper(),根据学到的异步消息机制,我们可以知道Looper是Queue的管家,将队列中存在的信息取出,分发回handleMessage()中,我认为这个scheduleTraversals()的意义应该是在主线程中完成了一次View绘制,那我可不可以认为正在传递的消息就是View的绘制流程呢?我刚刚上下乱翻的时候见到了不少的xxImpl,Impl就是执行的缩写,我姑且试一下吧,搜搜Impl

    scheduleTraversals()的实现

    还真让我搜到了一个ViewRootImpl(),我估计这个应该就是View的绘制流程,然后我找到了可以证明这是View的绘制流程的圣物,绘制View的开端——performTraversals()!于是可以正式分析View的绘制流程了

    众所周知View的绘制流程分为3个最主要的阶段,onMeasure()、onLayout()和onDraw(),下面我就一个一个找出来配合其他博客做一些分析

    onMeasure()


    6.0版本onMeasure()似乎改名了,改叫measureHierarchy()

    MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格

    EXACTLY: 对子View提出了一个确切的建议尺寸(SpecSize)

    AT_MOST: 子View的大小不得超过SpecSize

    UNSPECIFIED: 对子View的尺寸不作限制,通常用于系统内部

    measureHierarchy()确定了绘制的矩形的规格和大小,由getRootMeasureSpec()获取到了最外层的视图(根视图),再把获取到的大小和规格赋给childWidthMeasureSpec和childHeightMeasureSpec,我们切进去看看里面写了什么

    getRootMeasureSpec

    我们可以观察到lp.width和lp.height在创建出Viewgroup实例的时候被赋值了,默认为MATCH_PARENT,MeasureSpec调用了makeMeasureSpec组装一个MeasureSpec,当rootDimension等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST,都不是的时候就由开发人员来决定大小。MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的

    把通过makeMeasureSpec规定好的高和宽传给performMeasure(),切进去看一下他的源码

    mView就是大小和规格已经确定的View,调用它的measure()开始测量,切进去看看

    调用measure()来算出一个View应该为多大。参数为父View对其宽高的约束信息。

    什么情况下才会开始测量呢?可以看出是在需要测量或者强制测量时开始测量,如果这时发现缓存中存在已经测量好的View就不在测量,如果没有View的缓存就调用onMeasure()开始真正的实际测量,最后将测量好的结果作为最终值返回,那么实际测量又是怎样测量的呢?切进去看看

    可以看到实际测量工作由getDefaultSize()完成,用它来获取View的大小,但是在深入下去路就断了,我查了百度后才知道实际执行测量工作的是FrameLayout的onMeasure(),实际测量是这样的,先通过measureChildWithMargins()测量子View,在测量自身,还要把padding的部分也算进去,这样就能测量出一个能够正常显示自身及其所有子View的结果,再调用resolveSizeAndState()把测量好的结果缓存起来,最后就通过getDefaultSize()获取默认尺寸,完成实际测量

    需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取View测量出的宽高,以此之前调用这两个方法得到的值都会是0。

    View大小的控制是由父View、布局文件(MATCH_PARENT和WRAP_CONTENT)、以及View本身共同完成的,父View会提供给子View参考的大小,而开发人员可以在XML文件中指定View的大小,然后View本身会对最终的大小进行拍板

    onLayout()


    经过了一轮测量,View的大小已经测量好了,需要对View进行布局,就是确定View的位置,接着就会调用View里的layout(),我们切进去看一下吧

    layout()接收4个参数代表着上(top)下(below)左(left)右(right),这个坐标是相对于当前的父布局而言的。chang判断View的大小是否变化,是否要对当前View进行重绘。同时还会在这里把传递过来的四个参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量,接下来是重头戏onLayout(),切进去看看

    怎么是个空方法???经过百度查证,这个确实是个空方法!因为onLayout()是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父View决定子View的显示位置。所以我们来看下ViewGroup中的onLayout()方法是怎么写的吧

    ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。没错,像LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的,这些布局源码都比较复杂,有兴趣你可以去看看

    onDraw()


    measure和layout的过程都结束后,接下来就进入到draw的过程了,从名称上来看你可以得知这才是真正的绘制View的时候,老方法搜一下draw,一下子就出来了

    总共有7个步骤,但是注释里写着跳过2和5步骤,搜了一下说是不常用....不过这是我见到的最言简意赅的一次了.....

    步骤1:如果需要,绘制背景

    步骤3:绘制内容

    步骤4:绘制子View

    步骤6:绘制滚动条等

    步骤7:绘制默认焦点高亮

    当你切进去看这些源码内容时, 你会发现View是不会帮我们绘制内容部分的,因此需要每个View根据想要展示的内容来自行绘制,绘制的方式主要还是通过Canvas类

    总结


    简单总结一下,View的绘制流程简单来说就是在屏幕上绘制一块矩形的区域,首先执行onMeasure测量View的大小,如果有缓存过的View就用缓存里的,没有就用measure重新测量,父View会给子View提供一个参考,程序员可以通过xml文件来主动设置View的大小,View的大小最终由View自身决定。执行完成后开始执行onLayout,确定好View的位置,而onLayout是个抽象方法必须要重写。最后执行onDraw来进行绘制,就是通过Canvas类来实现各种各样的绘制,但是View不会绘制内容部分,每个View需要根据自己想要的内容自行绘制。写到这里就算完篇了,有不少东西看的不是很懂,只能根据网上的东西猜到大概的意思。如果你觉得看完本篇还不满意的话,我建议你下个源码去读一读吧,说不准你能有不小的收获

    相关文章

      网友评论

        本文标题:Android的View绘制流程

        本文链接:https://www.haomeiwen.com/subject/pprnlftx.html