View 是 Android 中所有控件的基类,View本、身可以是单个控件也可以是由多个控件组成的一组控件,通过这种关系就形成了View 树的结构。
View 的工作原理主要包含 View 的三大流程onMeasure()、onLayout()和onDraw() 。
ViewRoot和DecorView
onCreate 里执行 setContentView 之后 View 是如何显示到屏幕上的:
当调用 Activity 的 setContentView 方法后会调用PhoneWindow 类的 setContentView方法,PhoneWindow类是抽象类Window的实现类,Window 类用来描述Activity 视图最顶端的窗口显示和行为操作,setContentView 方法中最终会生成一个DecorView 对象,DecorView 是 PhoneWindow类的内部类,继承自FrameLayout ,所以调用Activity 方法 setContetnView 后最终会生成一个FrameLayout 类型的 DecorView 组件,该组件将作为整个应用窗口的顶层图,然后在DecorView 容器中添加根布局,根布局中包含一个id 为 content 的 FrameLayout 内容局,我们的Activity 加载的布局xml 最后通过LayoutInflater 将 xml 内容布局解析成 View树形结构,最后添加到id 为 content 的 FrameLayout布局当中,至此,View 最终就会显示到手机屏幕上。
获取content以及view的方法:
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
content.getChildAt(0);
ViewRoot
ViewRoot 对应的实现类是ViewRootImpl 类,他是连接WindowManager 和DecorView 的纽带,view 的三大流程均是通过ViewRoot 来完成的。View 的绘制流程是从ViewRoot 的performTraversals 方法开始的,它经过measure、layout、draw 三个过程才能最终将一个View 绘制出来,其中measure 用来测量View 的宽和高,layout 用来确定View 在父容器的放置位置,而draw 则负责将View 绘制在屏幕上。
measure 过程决定了view 的宽高,在几乎所有的情况下这个宽高都等同于view 最终的宽高。layout 过程决定了view 的四个顶点的坐标和view实际的宽高,通过 getWidth 和 getHeight 方法可以得到最终的宽高。draw过程决定了view的显示,只有draw方法完成后View的内容才能呈现在屏幕上。
MeasureSpec
MeasureSpec包含了SpecMode(测量模式)和SpecSize(规格大小),
可以通过int mode =MeasureSpec.getMode(widthMeasureSpec)
得到模式,
用int size = MeasureSpec.getSize(widthMeasureSpec)
得到尺寸。
SpecMode
SpecMode有三类:
UNSPECIFIED(父容器不对View有限制)
EXACTLY(精确模式,此时View的最终大小就是SpecSize所指定的值,对应match_parent和指定具体数值两种模式)
AT_MOST(最大模式,父容器指定了一个SpecSize,View的大小不能大于这个值)
总结
对于顶级View(DecorView),其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同决定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams以及View的margin、padding共同决定,MeasureSpec确定后,onMeasure就可以确定View的测量宽高。
当view采用固定宽高时,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式,并且大小是LayoutParams中的大小。
当view的宽高是match_parent时,如果父容器的模式是精确模式,那么view也是精确模式,并且大小是父容器的剩余空间;如果父容器是最大模式,那么view也是最大模式,并且大小是不会超过父容器的剩余空间。
当view的宽高是wrap_content时,不管父容器的模式是精确模式还是最大模式,view的模式总是最大模式,并且大小不超过父容器的剩余空间。