final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//得到子元素的 MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
- widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin - heightUsed, lp.height);
//对子View 开始进行 measure
child.measure(childWidthMeasureSpec,
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
childHeightMeasureSpec);
}
复制代码
上述方法首先会拿到子 View 的 LayoutParams 布局中定义的参数,然后根据父容器的 MeasureSpec 、 子元素的 LayoutParams 、子元素的 padding 等然后拿到对子元素的测量规格,可以看 getChildMeasureSpec 代码具体实现:
//ViewGroup.java
//padding:指父容器中已占用的大小
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//拿到在父容器中可用的最大的尺寸值
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//根据父容器的测量规格和 View 本身的 LayoutParams 来确定子元素的 MeasureSpec
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
复制代码
这一段代码虽然比较多,但是还是比较容易理解,它主要的工作就是先拿到在父容器中可用的尺寸,然后根据父元素的测量规格和子元素中 LayoutParams 参数来决定当前子 View 的 MeasureSpec。
上面的代码,如果用一张表格来表示的话,应该更好理解,请看下表:
parentSpecMode / childLaoutParams | EXACTLY(精准模式) | AT_MOST(最大模式) | UNSPECIFIED(精准模式) |
---|---|---|---|
dp/px | EXACTLY childSize | EXACTLY childSize | EXACTLY childSize |
match_parent | EXACTLY parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
Warap_content | AT_MOST parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
通过此表可以更加清晰的看出,只要提供父容器的 MeasureSpec 和子元素的 LayoutParams, 就可以快速的确定出子元素的 MeasureSpec 了,有了 MeasureSpec 就可以进一步确定出子元素测量后的大小了。
View 工作流程
View 的工作流程主要是指 measure、layout 、draw 这三个流程,即 测量 -> 布局 -> 绘制,其中 measure 确定 View 测量宽高,layout 确定 View 的最终宽高和四个顶点的位置,而 draw 则将 View 绘制到屏幕上。
在讲解 View 的绘制流程之前,我们有必要知道 View 的 measure 何时触发,其实如果对 Activity 生命周期源码有所了解的应该知道,在 onCreate 生命周期中,我们做了 setContentView 把 XML 中的节点转为 View 树的过程,然后在 onResume 可以交互的状态,开始触发绘制工作,可以说 Activity 的 onResume 是开始绘制 View 的入口也不为过,下面看入口代码:
//ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
…
/**
- 1. 最终会调用 Activity onResume 生命周期函数
/
r = performResumeActivity(token, clearHide, reason);
…
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
/* - 2. 调用 ViewManager 的 addView 方法
*/
wm.addView(decor, l);
} else {
…
} else {
…
}
}
复制代码
通过上面代码我们知道,首先会调用注释 1 performResumeActivity 方法,其内部会执行 Activity onResume 生命周期方法, 然后会执行将 Activity 所有 View 的父类 DecorView 添加到 Window 的过程,我们看注释 2 代码它调用的是 ViewManager#addView 方法,在讲解 WindowManager 源码的时候,我们知道了 WindowManager 继承了 ViewManager 然后它们的实现类就是 WindowManagerImpl 所以我们直接看它内部 addView 实现:
//WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
/**
- 委托给 WindowManagerGlobal 来处理 addView
*/
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
复制代码
内部处理又交给了 WindowManagerGlobal 对象,我们继续跟踪,代码如下:
//WindowManagerGlobal.java
/**
*
- @param view DecorView
- @param params
- @param display
- @param parentWindow
*/
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException(“view must not be null”);
}
if (display == null) {
throw new IllegalArgumentException(“display must not be null”);
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException(“Params must be WindowManager.LayoutParams”);
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
/**
- 1. 根据 WindowManager.LayoutParams 的参数来对添加的子窗口进行相应的调整
*/
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
…
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {