《View的工作原理》笔记


[Disclaimer]: 以下是读Chapter4的笔记

4.1 ViewRoot和DecorView

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

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

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

《View的工作原理》笔记_第1张图片
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概念

《View的工作原理》笔记_第2张图片
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这样的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:


《View的工作原理》笔记_第3张图片
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的工作原理》笔记)