structure
- DecorView是一个应用窗口的根容器,它本质上是一个FrameLayout
- DecorView有唯一一个子View,它是一个垂直LinearLayout,包含两个子元素,
- 一个是TitleView(ActionBar的容器),
- 另一个是ContentView(窗口内容的容器)
setContentView
这个方法只是完成了Activity的ContentView的创建,而并没有执行View的绘制流程。
- 实际上调用到了PhoneWindow的setContentView()方法。
- PhoneWindow的setContentView()方法中
- 调用了LayoutInflater的inflate()方法来填充布局
- 传入了decorView作为LayoutInflater.inflate()的root参数
- 最终调用的是inflate(XmlPullParser, ViewGroup, boolean)方法来填充布局
- 单独处理merge标签
- 调用rInflate()方法来递归填充布局
- inflate()和rInflate()方法中都调用了rInflateChildren()方法
- rInflateChildren()方法实际上调用了rInflate()方法
inflate方法总结
- XML中保存了ViewTree的结构和View的相关标签信息(包括View的类型和一些属性值)
- 这些信息会在后面通过反射的方式(如果没有Factory2和Factory的话)创建实例对象
- 如果创建的是ViewGroup,则会对它的子View遍历重复创建步骤
- 创建完View对象后,会add到对应的ViewGroup中
- 这些信息会在后面通过反射的方式(如果没有Factory2和Factory的话)创建实例对象
- inflate->rInflate->createViewFromTag->createView
view绘制的起点
- View的绘制是由ViewRoot来负责的。每个应用程序窗口的decorView都有一个与之关联的ViewRoot对象,这种关联关系是由WindowManager来维护的。
当建立好了decorView与ViewRoot的关联后,ViewRoot类的requestLayout()方法会被调用,以完成应用程序用户界面的初次布局。实际被调用的是ViewRootImpl类的requestLayout()方法
- setContentView() 只是把 View 添加到 DecorView 上
- onResume() 中 ViewRootImpl 和 DecorView 做了关联
- requestLayout() 和 invalidate() 会触发 ViewRootImpl 绘制 View
在 Activity 的 onResume() 方法执行后,DecorView 会被添加带 ViewRootImpl 中。然后执行 requestlayout()
//H:Handler->handleMessage(RESUME_ACTIVITY)->handleResumeActivity->windowmanager.addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
……
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
……
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView); //invoke requestLayout
} ……
}
MeasureSpec
exactly优先,at_most次之
MeasureSpec代表一个32位int值,
- 高2位代表SpecMode,SpecMode是指测量模式,
- UNSPECIFIED, 指父容器不对view有任何限制,要多大给多大,一般用于系统内部,表示测量的状态
- EXACTLY, 父容器检测出view需要的精确大小,view的最终大小就是SpecSize的大小。对应LayoutParams中的match_parent模式
- AT_MOST,父容器指定了一个SpecSize, view的大小不能大于这个值, 对应LayoutParams.wrap_content模式
- 低30位代表SpecSize,而SpecSize是指在某种测量模式下的规格大小
- MeasureSpec 不是 唯一 由 LayoutParams 决定 的, LayoutParams 需 要和 父 容器 一起 才能 决定 View 的 MeasureSpec, 从而 进一步 决定 View 的 宽/ 高
- if(view固定宽/高)— 不受父容器,View的MeasureSpec==EXACTLY,并且大小遵循LayoutParams中的大小
- if(view宽/高是match_parent)
- if 父容器是EXACTLY, view=EXACTLY,大小是父类的剩余空间
- if 父容器是AT_MOST,view=AT_MOST,不能超过父容器的剩余空间
- if(view宽/高是wrap_content),不受父容器影响,view=AT_MOST,不能超过父容器的剩余空间
UNSPECIFIED-主要 用于 系统 内部 多次 Measure 的 情形, 一般来说, 我们 不需要 关注 此 模式。
- 对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定
- 对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高
三阶段
获取宽高的时机
在onCreate、onStart、onResume中均无法正确得到某个View的宽/高信息,这是因为View的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕了,如果View还没有测量完毕,那么获得的宽/高就是0
- Activity/ View# onWindowFocusChanged,
- view. post( runnable),
- ViewTreeObserver,
- view. measure( int widthMeasureSpec, int heightMeasureSpec), 根据 View 的 LayoutParams 来 分情况,是否有效
- match_parent, 直接放弃,无效
- 具体数值(dp/px)—ok
- wrap_content—ok
object ViewUtil {
/**
* view的layoutparams指定了宽高,可以通过这个方法来获取真实宽高, 譬如100px
*/
fun measureViewOfExactly(view: View) {
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, EXACTLY)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
view.measure(widthMeasureSpec, heightMeasureSpec)
}
/**
* view的layoutparams是wrap_content,可以通过这个方法来获取真实宽高
*/
fun measureViewOfWrapContent(view: View) {
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 shl 30) - 1, AT_MOST)
//view的 尺寸 使用 30 位 二进制 表示, 也就是说 最大 是 30 个 1( 即 2^ 30 - 1), 也就是( 1 << 30) - 1, 在最 大化 模式 下, 我们 用 View 理论上 能 支持 的 最大值 去 构造 MeasureSpec 是 合理 的。
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 shl 30) - 1, AT_MOST)
view.measure(widthMeasureSpec, heightMeasureSpec)
}
}
- 错误用法关于 View 的 measure, 网络 上有 两个 错误 的 用法。 为什么 说是 错误 的, 首先 其 违背 了 系统 的 内部 实现 规范( 因为 无法 通过 错误 的 MeasureSpec 去 得出 合法 的 SpecMode, 从而 导致 measure 过程 出错), 其次 不能 保证 一 定能 measure 出 正确 的 结果。
//第一 种 错误 用法:
int widthMeasureSpec = MeasureSpec. makeMeasureSpec(- 1, MeasureSpec. UNSPECIFIED);
int heightMeasureSpec = MeasureSpec. makeMeasureSpec(- 1, MeasureSpec. UNSPECIFIED);
view. measure( widthMeasureSpec, heightMeasureSpec);
//第二 种 错误 用法:
view. measure( LayoutParams. WRAP_ CONTENT, LayoutParams. WRAP_ CONTENT)
View的测量宽/高和最终宽/高有什么区别
在View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于View的measure过程,而最终宽/高形成于View的layout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。因此,在日常开发中,我们可以认为View的测量宽/高就等于最终宽/高,但是的确存在某些特殊情况会导致两者不一致
layout
- 当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。
- layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置,
draw
- view的绘制过程
- 绘制 背景 background. draw( canvas)
- 绘制 自己( onDraw)
- 绘制 children( dispatchDraw)
- 绘制 装饰( onDrawScrollBars)
- invalidate(), 请求重绘 View 树,即 draw 过程,假如视图发生大小没有变化就不会调用
layout()
过程,并且只绘制那些调用了invalidate()
方法的 View - requestLayout(), 当布局变化的时候,比如方向变化,尺寸的变化,会调用该方法,在自定义的视图中,如果某些情况下希望重新测量尺寸大小,应该手动去调用该方法,它会触发
measure()
和layout()
过程,但不会进行 draw。