美文网首页
《View的工作原理》笔记

《View的工作原理》笔记

作者: DrunkPian0 | 来源:发表于2018-01-23 18:21 被阅读88次

    [Disclaimer]: 以下是读<Android开发艺术探索>Chapter4的笔记

    4.1 ViewRoot和DecorView

    ViewRoot对应ViewRootImpl类,是连接WindowManagerDecorView的纽带,View的三大流程都是通过ViewRoot完成的。

    WMS调用ViewRootImpl#performTraverals方法开始View的测量、布局、绘制流程。

    measure: 测量宽高
    layout: 确定位置
    draw: 绘制在屏幕上

    performTraversals的工作流程

    理解这个图:
    WMS调用ViewRootImpl#performTraverals来执行ViewGroup的
    performMeasure->measure->onMeasure, 然后ViewGroup的onMeasure里会调用子View的measure,重复下去。layout和draw的流程也类似。

    好了,下面说下结果。
    measure结束后可以通过getMeasuredWidth和getMeasureHeight获取View测量后的宽高,一般就是View的最终宽高。

    layout完成后可以调用getTop等方法获取四个角的位置。

    draw完成后View才会显示在屏幕上。


    关于DecorView:DecorView是一个FrameLayout,不多说了,本文前面的图吧。

    4.2 理解MeasureSpec

    4.2.1 MeasureSpec概念

    Documentation MeasureSpec in View

    MeasureSpec是View的静态内部类。
    是一个32 bit的int值,高2位代表SpecMode,低30位为代表SpecSize。
    MeasureSpec囊括了从Parent到child的layout requirements。
    每个MeasureSpec代表一个宽或高的需求。
    一个MeasureSpec由size和mode组成。有三种可能的modes:
    UNSPECIFIED
    parent没有给child施加任何限制。它可以是任何size。
    (一般用于系统内部,表示测量状态)

    EXACTLY
    parent为child决定了精确的size。
    (对应于LayoutParams的match_parent和具体数值)

    AT_MOST
    child可以任何大小,最大不超过指定size。
    ((对应于LayoutParams的wrap_content)

    MeasureSpec被是现成ints来减少对象分配。这个类用来pack和unpack<size, mode>这样的tuple到int型。

    4.2.2 MeasureSpec和LayoutParams的关系

    首先,系统根据MeasureSpec来进行Measure过程。
    MeasureSpec怎么来?
    LayoutParams决定着MeasureSpec。但MeasureSpec不是仅仅由LayoutParams决定的,需要和父容器一起才能决定View的MeasureSpec。

    View Measure的时候,系统将LayoutParams在parent约束下转换成MeasureSpec,再根据MeasureSpec确定测量后的宽/高。

    子元素的MeasureSpec的创建与父容器的MeasureSpec和View的padding、margin有关。

    4.3 View的工作流程

    4.3.1 Measure的工作过程

    这边分成两个部分,View和ViewGroup的Measure。前者只是Measure一次,后者要递归Measure。具体细节不讲了。
    提一点:ViewGroup是抽象类,所以onMeasure是由子类实现的。
    比如LinearLayout跟RelativeLayout的onMeasure是就是不同的,LinearLayout要计算totalLength嘛。


    Measure完成后可通过getMeasuredHeight/Widht获取宽高,极端情况下系统需要多次Measure才能确定宽高,所以最好在onLayout中去获取宽高。

    下面说常见的一个问题,就是在Activity启动的时候就测量宽高,但是在onCreate, onStart, onResume里都是无法获取宽高的,具体我前面也分析过了,ActivityThread#handleResumeActivity才会执行onResume,WMS才会调用ViewRootImpl#performTraversals。而且View的measure过程和Activity的生命周期不是同步执行的。如果View还没有测量完,那获得的宽高就是0。有4中方法解决这个问题:

    1. Activity/View#onWindowFocusChanged
      这个方法意思是View已经初始化完毕了。但是这个方法在Activity的窗口得到和失去焦点都会被调用一次。不过这个回调函数里有个参数是boolean的hasFocus,所以可以在它为true的时候获取宽高。

    2. view.post(runnable)
      通过post可以将runnable投递到消息队列尾部(跟handler一样),然后等待Looper调用这个Runnable的时候,View已经初始化好了。

    3. ViewTreeObserver

    ViewTreeObserver observer = view.getViewTreeObserver();
    

    会 return mAttachInfo.mTreeObserver;
    然后往observer上add各种listener:


    ViewTreeObserver#add

    比如onGlobalLayoutListener,会在View Tree状态改变或是内部可见性改变的时候回调。

    1. view.measure(int WidthMeasureSpec, int HeightMeasureSpec)
      手动对View进行measure,比较复杂,需要根据LayoutParams分情况讨论。

    4.3.2 layout过程

    Layout的作用是ViewGroup确定子元素位置。先是确定ViewGroup的位置,然后会在onLayout里遍历调用子元素的layout,然后onLayout又被调用。layout确定view本身位置,onLayout确定子元素位置。

    步骤大概如下:
    setFrame确定4个顶点的位置,然后调用ViewGroup#onLayout。同样是没有实现的,跟具体布局有关。

    getWidth(Height)和getMeasuredWidth(Height)的区别
    姑且把前者叫作最终宽,后者叫作测量宽。它们的区别只是后者是在measure过程中赋值的,前者是在layout过程中赋值的。比如getWidth其实就是return mRight - mLeft。
    除非覆写onLayout然后搞些赋值(这种操作没什么意义),否则一般来讲测量宽和最终宽的值是一样的。

    4.3.3 draw(canvas)过程

    将View绘制到图片上。遵循如下几步:
    (1) background.draw(canvas) 绘制背景
    (2) onDraw 绘制自己
    (3) dispatchDraw 绘制children
    (4) onDrawScrollBars 绘制装饰


    by DrunkPiano

    相关文章

      网友评论

          本文标题:《View的工作原理》笔记

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