本篇就Android中View的测量整体流程简单记录如下<下面涉及到的Android源码均为API23的源码>
对于View树的绘制起始点可以追溯到在ActivityThread的performResumeActivity()–>WindowManagerGloble.addView()–>new RootViewImpl()+root.setView(DecorView)–>RootViewImpl.setView()—->RootViewImpl.requestLayout()以上步骤在第一篇文章中均可以找到,最终调用到RootViewImpl.requestLayout()方法
下面我们就从RootViewImpl.requestLayout()开始:
@ViewRootImpl.java
@Override
//-----------1、在setView()中有调用该方法,开始进行视图绘制----------
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();//schedule:安排 Traversals:穿越
}
}
//---------------2、异步调用绘制视图 Choreographer:Coordinates the timing of animations, input and drawing.---------
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//--------***mTraversalRunnable 是一个TraversalRunnable类型可执行对象,视图绘制专用线程***-------------
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
//-------------3、TraversalRunnable定义-------------------
@ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//在该处调用doTraversal()
doTraversal();
}
}
//--------------4、mTraversalRunnable初始化---------------
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
//--------------5、一般常见的View开始绘制的调用performTraversals();在此方法中调用--------------
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();//---------6.performTraversals()----
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
@Window
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
@WindowManager.LayoutParams
//LayoutParams 的默认构造函数实现如下,可知其宽高默认都是LayoutParams.MATCH_PARENT的
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
我们在View中调用的requestFitSystemWindows()最终会调用到ViewRootImpl中的requestFitSystemWindows() 并设置mApplyInsetsRequested 为true,大致是在设置了Window的属性之后例如SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 属性之后应该调用一次该方法,以保证得到我们想要的效果
@ViewRootImpl.java
private void performTraversals() {//---------2.2.1 经过上面的逻辑最终会调用到此处performTraversals()
// cache mView since it is used so much below...
final View host = mView;
WindowManager.LayoutParams lp = mWindowAttributes;//------2.2.2下面measureHierarchy()方法中调用的getRootMeasureSpec()方法参数中的lp 实际即为当前Window的LayoutParams
int desiredWindowWidth;//下面measureHierarchy()方法中调用的getRootMeasureSpec()方法参数中宽度大小,实际即为屏幕的宽
int desiredWindowHeight;//下面measureHierarchy()方法中调用的getRootMeasureSpec()方法参数中高度大小,实际即为屏幕的高
...
if(mFirst){
....
//----2.2.3初始化desiredWindowWidth和desiredWindowHeight 对应下面measureHierarchy()第一个参数初始化位置;对于普通Activity即为屏幕的宽高
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
....
host.dispatchAttachedToWindow(mAttachInfo, 0);//------2.2.4、该处最终会回调到Activity的onAttatchWindow()
}
...
if (mApplyInsetsRequested) {//-----2.2.5 mApplyInsetsRequested该值是在requestFitSystemWindows()方法中设置的,我们在View中调用的requestFitSystemWindows()最终会调用到ViewRootImpl中的requestFitSystemWindows() 并设置mApplyInsetsRequested 为true,大致是在设置了Window的属性之后例如SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 属性之后需要调用一次该方法
mApplyInsetsRequested = false;
mLastOverscanRequested = mAttachInfo.mOverscanRequested;
dispatchApplyInsets(host);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
// we don't need to go through two layout passes when things
// change due to fitting system windows, which can happen a lot.
//----------2.2.6在此处调用measureHierarchy()开始measure 此处的lp为WindowManager.LayoutParams desiredWindowWidth为 屏幕宽度 desiredWindowHeight为屏幕高度---------
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
}
}
...
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
-----2.2.7拿取根部局的MeasureSpec-----
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//---1.拿取根部局的MeasureSpec---
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);//---2.拿取根部局的MeasureSpec---
if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//------2.2.8正式测量确认各子View的尺寸------
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {//--------如果权重值不管是横向的还是竖向的都需要再次measure()
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {//----------如果权重值不管是横向的还是竖向的都需要再次measure()
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {//---------如果需要再次测量
if (DEBUG_LAYOUT) Log.v(TAG,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ------2.2.9 如果Window的Layoutparams中设置的有权重值则会根据权重再次测量
}
layoutRequested = true;
}
}
} else {//???--wtf如果不是第一次测量&&window/insets/visibility都没有变化,只是窗口有移动
...//省略代码
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);//------2.2.10、执行layout,会回调所有ViewGroup及View中的onLayout()-------------
//By this point all views have been sized and positioned 到此则所有View的大小及位置都已经确定完毕
// We can compute the transparent area
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
//----##首先确认DecorView在Window中的位置##-----
host.getLocationInWindow(mTmpLocation);
...
}
}
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mViewVisibility = viewVisibility;
...
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();//------------2.2.11该方法中会回调所有onDraw()-----
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {//当Window设置的为WrapContent时
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
....
}
if (!goodMeasure) {//一般的Activity中的Window在初始化时都是ViewGroup.LayoutParams.MATCH_PARENT的,上面的逻辑不会走直接走到此处
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//---------执行perforMeasure()-----------
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
}
return windowSizeMayChange;
}
//----------至此开始调用DecorView的measure()方法,Activity中一般给入的参数都是屏幕的实际宽高和MeasureSpec.EXACTLY合成的MeasureSpec
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//----2.3.1 该mView正是DecorView
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
View的测量开始是从View.measure()方法开始的,我们先理清一下View树的测量逻辑
- 1.对于顶级DecorView的measure()我们在2.2中已经看到是在ViewRootImpl中触发,然后会调用的DecorView的onMeasure()方法(需要明确的是DecorView是一个ViewGroup);
- 2.对于一个子View(不管是单个的View还是一个ViewGroup)其measure()方法的触发逻辑为:当其父View在执行onMeasure的时候会依次调用测量子View,最终会调用到子View的measure()方法。
- 3.如果子View是一个普通的View,当触发其onMeasure之后(该过程可能执行不止一次),该View会根据传入的MeasureSpecMode和自己的测量规则算出自己的大小(只算大小,不管位置),并调用setMeasuredDimension()保存自己的测量出来的大小。
- 4.如果子View还是一个ViewGroup,则会再次在其onMeasure()方法中按照实际的逻辑依次触发子View的measure()方法,并调用setMeasuredDimension()保存自己的测量出来的大小;
- 5.进一步依次递归
@View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//测量完成后调用setMeasuredDimension(),只有在调用该方法之后View的getMeasureWidth()才会有值
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
对于常见的Activity,DecorView默认其是由屏幕的实际宽高和MeasureSpec.EXACTLY合成的,具体其他的情况可以参考源码查看
@ViewRootImpl.java
private static int getRootMeasureSpec(int windowSize, int rootDimension) {//---此处的rootDimension默认是LayoutParams.MATCH_PARENT
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
一般对于Activity来说其WindowSize即为屏幕的宽高;rootDimension则对应为WindowManager.LayputParams,因为一般默认系统生成的PhoneWindow都会设定为Match_PARENT,因此Mode就对应为就对应MeasureSpec.EXACTLY。但是该LayoutParams是可以改变的,我们比较常用的手动设置的场景是在自定义Dialog时根据需要设置Dialog的Window的相关参数
在ViewGroup中不同的ViewGroup实现的onMeasure由于工作机制不同,其measureChildren()和measureChild()的实现也不相同,默认实现如下:
@ViewGroup.java
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
//measureChild的默认实现如下
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
但是不管ViewGroup的具体实现是什么,最终都会调用getChildMeaureSpec()来生成子View所需的MeasureSpec,实现如下:
@ViewGroup.java
//getChildMeasureSpec()实现如下
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;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
而该getChildMeasureSpec(int spec, int padding, int childDimension)的逻辑则可以总结为我们经常能够见到的一张表格:
首先:我们带着以下几个问题来看其测量过程
@LinearLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);//设置Orientation = VERTICAL 时会使用该方法进行测量
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
具体LinearLayout的代码及相应的简单注释如下:
@LinearLayout.java
//竖直布局时使用的测量方法
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;//---1.用来记录当前LinearLayout已经占用的高度(或者简单的说每测量一个子View该值都会加上该子View的高度)
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;//---2.权重值统计
final int count = getVirtualChildCount();//-----3.拿取子View的个数,但不一定是最终子View的个数,子View的Visible属性为GONE时会直接跳过不再测量
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);//----4.当前布局的测量模式--对于该LinearLayout的子View来书这个即使父布局的测量模式,在合成子View的MeasureSpecMode的时候父布局的测量模式即使从此处得到
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);//当前布局的测量模式
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.----5、 开始所有子View的测量,ViewGrop的测量师递归的即如果子View也是一个ViewGroup则会直接进入该子View其内部进行测量
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {//----6、如果子View的Visibility属性设置为GONE则统计后直接continue
i += getChildrenSkipCount(child, i);
continue;
}
if (hasDividerBeforeChildAt(i)) {//------7、是否有Divider
mTotalLength += mDividerHeight;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();//----8、拿取子View的LayoutParams
totalWeight += lp.weight;//统计子View设置的权重值
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {//----9.如果LinearLayout的SpecMode为EXACTLY&&子View的height ==0 且子View设置的weight>0(简单书就是如果当前的Linearlayout的高度尺寸已经确定同时该子View的高度设置为0且该子View的weight值不为0)---
// Optimization: don't bother measuring children who are going to use
// 优化:不再过度的去测量将会使用剩余空间的子View
// leftover space. These views will get measured again down below if
//这些子View将会在下面再次得到测量当还有剩余空间时
// there is any leftover space.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;//测量子View过程中是否有跳过
} else {//----10、否则
int oldHeight = Integer.MIN_VALUE;
if (lp.height == 0 && lp.weight > 0) {//----11、如果子View的Weight>0(即有权重值)且设置的高度为0
// heightMode is either UNSPECIFIED or AT_MOST, and this
//不管当前布局的测量模式是 ATMOST还是UNSPECIFIED(不可能再会是EXACTLY),
// child wanted to stretch to fill available space.
//并且该子View想要填充当前Linearlayout剩余的有效空间。
// Translate that to WRAP_CONTENT so that it does not end up
//则设置该子View的LayoutParams为WrapContent
// with a height of 0
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;//设置子View为WrapContent
}
// Determine how big this child would like to be. If this or
//确认该子View想要得到多大的空间如果该子View或者之前有子View设置过Weight,
// previous children have given a weight, then we allow it to
//那么会允许子View得到所有的有效空间(但是如果需要的话我们在后面可能会收缩该空间)
// use all available space (and we will shrink things later
// if needed).
measureChildBeforeLayout(//
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
/**
* If applicable, compute the additional offset to the child's baseline
* we'll need later when asked {@link #getBaseline}.
*/
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
// if we are trying to use a child index for our baseline, the above
// book keeping only works if there are no children above it with
// weight. fail fast to aid the developer.
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);// ------heightSizeAndState 确认
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
int delta = heightSize - mTotalLength;//----11、算出之前没有跳过的所有子View已经占据的空间打小,注意当子View设置为WrapContent weight>0时,此时mTotallenrth中是包含实际按AtMost算出来的大小值的,该处可以说明weight是所有子View计算之后剩余子View的剩余大小再按权重分配的---
//-----12、到该步骤则View大致是对应9.1的条件满足还没有进行过测量的,以及不管是否有weight值,但是已经按照相应的测量模式测量过一次了的
if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
//----13、循环遍历子View 根据相应的规则进行重新测量
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {//14、如果子View的权重值大于0 按以下机制再次计算,------------
// Child said it could absorb extra space -- give him his share
int share = (int) (childExtra * delta / weightSum);//12.计算需要该子View的weight所需要占用的剩余空间
weightSum -= childExtra;
delta -= share;
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);
// TODO: Use a field like lp.isMeasured to figure out if this
// child has been previously measured
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {//此处逻辑(对应之前跳过没有测量的View):子View满足lp.weight>0的同时lp.height!=0或者当前LinearLayout的SpecMode不是EXACTLY
//此处对应一种情况是当weight>0 lp.height为wrapcontent时,当前LinearLayout为EXACTLY时,得到的效果即是,该子View的大小为按照权重得到的剩余空间+上面按照ATMOST已经测量出来的大小。我们可以设置该多个子View的LayoutParamas为WrapContent同时设置其weight都为1,会看到子View的所占的高度是不等的,但是其减去实际占的位置之后,剩余的空间是相等的
// child was measured once already above...
// base new measurement on stored values
int childHeight = child.getMeasuredHeight() + share;//把剩余的空间按照比例添加到子View上
if (childHeight < 0) {
childHeight = 0;
}
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));//重新测量子View
} else {//此处逻辑是子View满足(lp.weight>0&&lp.height==0&&当前Linearlayout的heightSpecMode==EXACTLY)简单的说即是上面直接跳过的没有进行过测量的子View
// child was skipped in the loop above.
// Measure for this first time here
// 一开始跳过的子View此时第一次测量 尺寸大小为根据剩余尺寸和所占的权重计算出来的size大小,SpecMode为EXACTLY
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));
}
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));//再次统计当前Linearlayout的高度
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
} else {//----如果没有跳过任何子View的测量的则执行以下逻辑----
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
// We have no limit, so make all weighted views as tall as the largest child.
// Children will have already been measured once.
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {//----15、如果没有跳过任何子View的测量的则执行以下逻辑,且满足设置了使用useLargestChild为true且当前的LinearLayout的测量模式不为MeasureSpec.EXACTLY----
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {//如果有权重则再次测量其所占比例
child.measure(
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight,
MeasureSpec.EXACTLY));
}
}
}
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);//----16、当前LinearLayout测量完毕 调用setMeasuredDimension
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
对于LinearLayout如果一旦子View设置了weight,并且在当前LinearLayout的SpecMode不为EXACTLY的时候,会首先该子View会首先按照ATMOST+剩余有效空间大小合成子View的SpecMode进行一次测量,然后当所有子View测量完毕之后,会把剩余的空间按照该View占总权重的比例算出一个高度值,并把该值添加到已经测量出的高度值上与MeasureSpec.EXACTLY合成之后再次测量,此时子View的measure就不止调用一次,当让这只是一种情况。但说明measure调用的次数是和具体的测量策略有一定关系的
问题1中也已经回答了该问题,有一个比较特殊的场景可以验证,假设当前LinearLayout高满足测量模式为MeasureSpec.EXACTLY,有三个子View,当把所有子View设置为WrapContent,weight值为1时,如果三个子View的占用高度不同但是加起来当前LinearLayout仍有剩余空间时,则每个子View的实际测量高度为其有效占用高度+剩余空间的1/3,简单的各子View的最终测量出来的大小是不同的。(子View嵌套又有设置了Weight的子View的场景除外)。但是如果三个子View的高度设置为0,则会满足2.4.1中9.1的条件,此时最终子View的测量出来的高度值相同为当前LinearLayout高度的1/3。因此想要用weight值来让子View之间的间距看起来一样的时候,需要设置为WrapContent但是不建议使用此种方法,因为一旦子View中还有子View且设置了Weight值那么就不一样了。
这个我们在跟进代码setMeasuredDimension()之后就能清除看到只有测量完毕调用了该方法之后getMeasureWidth()和getMeasureHeight()才能拿到有效的值。
//setMeasuredDimension() 会调用到下面的方法
@View.java
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;//给mMeasuredWidth赋值,在这之后getMeasureHeight()才能拿取到值
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
mRight和mLeft则是在onLayout之后才能确定,所以getWidth()想要拿取到正确的值需要等到layout完毕之后才行
对于这个问题我们看代码:
@View.java
public final int getWidth() {
return mRight - mLeft;
}