我们已经知道了事件的传递机制,现在我们来看一下绘制的原理
我们已经知道View 的绘制是从 ViewRootImpl 的 requestLayout
开始,一直到 performTraversals
,
private void performTraversals() {
...
if (layoutRequested) {
...
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
...
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
...
if (!mStopped || mReportNextDraw) {
...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
...
}
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
...
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
} else {
if (isViewVisible) {
// 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) {
...
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
}
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
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;
}
MeaureSpec
Measure是一个32位的int,高2位代表SpecMode,低30位代表SpecSize。SpecMode表示测量模式,SpecSize指在某种测量模式下规格的大小。其表示如下:
高2位 | 含义 |
---|---|
0 | UNSPECIFIED |
1 | EXACTLY |
2 | AT_MOST |
用法
父类型 | 子类型 | 子Spec |
---|---|---|
EXACTLY | size | EXACTLY |
EXACTLY | MATCH_PARENT | EXACTLY |
EXACTLY | WRAP_CONTENT | AT_MOST |
AT_MOST | size | EXACTLY |
AT_MOST | MATCH_PARENT | AT_MOST |
AT_MOST | WRAP_CONTENT | AT_MOST |
接下来是performXXX
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again,
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
view.requestLayout();
}
}
});
}
}
}
}
private void performDraw() {
canvas = mSurface.lockCanvas(dirty);
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
...
finally {
try {
surface.unlockCanvasAndPost(canvas);
}
}
onMeasure
/**
* 这个方法就是用来测量宽高的. 被 {@link #measure(int, int)} 调用,
* 可以被子类覆写来提供有效和准确的测量数据
* 注意:当重写这个方法的时候,请必须调用 {@link #setMeasuredDimension(int, int)} 来存储测量到的宽高
* 否则,会由 {@link #measure(int, int)} 抛出 IllegalStateException 异常
* 如果这个方法被重写,由子类负责确保宽高起码是View 的最小宽高
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
这里的onMeasure 只是缺省定义,对于ViewGroup 来说有不同的Layout,有LinearLayout、RelativeLayout、FrameLayout 等,这些的 onMeasure 都由各自实现,在ViewGroup中定义了 measureChild 、 measureChildWithMargins 方法来对子视图进行测量,我们看到这两个方法都是先调用 getChildMeasureSpec 然后再调用子View 去 measure。而 measure 中真正起作用的是 onMeasure 方法,即各个layout 中的 onMeasure 方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
getChildMeasureSpec 通过参数传递的 MeasureSpec 得到 SpecMode 和 SpecSize, 然后根据计算出来的 SpecMode 和 参数 childDimension,来得到子 View 的 resultSize、resultMode 并计算出子 View 的MeasureSpec 。
onLayout
View 和 ViewGroup 的 layout 方法是有区别的,ViewGroup 的 layout 有final,即不可被继承,所以,View 可以重写 layout 和 onLayout 方法,这两个方法的参数只有一个区别 boolean changed;而 ViewGroup 只能重写 onLayout
所谓的 onLayout 即是根据 onMeasure 中测量的结果和一些padding、margin 来进行子View 的layout,具体的作用查看下面注释
/**
* Assign a size and position to a view and all of its
* descendants
*
* <p>This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().</p>
*
* 下面注释中提到子类不应该重写这个方法
* <p>Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.</p>
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
...
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
...
}
onDraw
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
public void draw(Canvas canvas) {
...
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
...
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
这里有两个重要方法 onDraw 和 dispatchDraw,在 onDrawForegroud 中还有一个 onDrawScrollbars,所以draw的调用栈是 draw -> onDraw | dispatchDraw | onDrawScrollBars, 具体 View 和ViewGroup 会有区别
invalidate 和 requestLayout
view的invalidate不会导致 ViewRootImpl 的 invalidate 被调用,而是递归调用父 view 的invalidateChildInParent,直到 ViewRootImpl 的 invalidateChildInParent,然后触发 peformTraversals,会导致当前 view 被重绘,由于 mLayoutRequested 为 false,不会导致 onMeasure 和 onLayout 被调用,而 OnDraw 会被调用
requestLayout 会直接递归调用父窗口的 requestLayout,直到 ViewRootImpl ,然后触发 peformTraversals ,由于 mLayoutRequested 为 true,会导致onMeasure 和 onLayout 被调用。不一定会触发OnDraw。如果触发了 onDraw 可能是因为在 layout
过程中发现l,t,r,b和以前不一样,那就会触发一次 invalidate,所以触发了onDraw,也可能是因为别的原因导致 mDirty
非空(比如在跑动画)
一般来说,只要刷新的时候就调用 invalidate,需要重新 measure 就调用requestLayout,后面再跟个invalidate(为了保证重绘)
RelativeLayout 和 LinearLayout 的 measure 对比
下面我们来看一下 RelativeLayout 和LinearLayout 的具体实现
首先是 LinearLayout 的 onMeasure -> measureVertical
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
...
// 主要是计算最大高度,顺便了解下最大宽度
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
...
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
// 注意1,如果 (height == 0 && weight > 0)
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// 优化操作: 即使子View使用的空间过量,对子View的测量也是没有影响的
// 因为稍后会重新测量
if (isExactly) {
mTotalLength += lp.leftMargin + lp.rightMargin;
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength +
lp.leftMargin + lp.rightMargin);
}
if (baselineAligned) {
final int freeWidthSpec = MeasureSpec.makeSafeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED);
final int freeHeightSpec = MeasureSpec.makeSafeMeasureSpec(
MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED);
child.measure(freeWidthSpec, freeHeightSpec);
} else {
skippedMeasure = true;
}
} else {
if (useExcessSpace) {
// heightMode 要么为UNSPECIFIED,要么为 AT_MOST。这里暂时使用WRAP_CONTENT表示,以便稍后能得到最佳的结果
lp.height = LayoutParams.WRAP_CONTENT;
}
// 根据weight 分配全部高度或没有高度
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
// 还记得前面我们把 height 设置为 WRAP_CONTENT么,这里我们把height 恢复 为 0
// 这样才能匹配EXACTLY 测量模式
// 与上面的相对应
lp.height = 0;
consumedExcessSpace += childHeight;
}
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
...
if (lp.weight > 0) {
/*
* 因为宽的值是假的,我们需要让它们保持独立
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
} // 第一次遍历结束
if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
// useLargestChild 代表一种模式,当设置为 true 的时候,所有的设置了 weight 的子view都会按照最大子View的大小测量
// 如果高度的模式是以下两种
// 我们需要重新计算一下最大高度
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
...
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
...
// 根据子View的 weight 扩展或收缩大小,
// 如果他们超出了边界,也就是之前的优化操作,
// 我们需要重新测量
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
if (skippedMeasure
|| ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
...
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final float childWeight = lp.weight;
if (childWeight > 0) {
...
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
// 这里就是第二次测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
...
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
mTotalLength += mPaddingTop + mPaddingBottom;
}
...
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
RelativeLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
//第一次测量
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);
applyHorizontalSizeRules(params, myWidth, rules);
measureChildHorizontal(child, params, myWidth, myHeight);
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
}
views = mSortedVerticalChildren;
count = views.length;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
// 第二次测量
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
applyVerticalSizeRules(params, myHeight, child.getBaseline());
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}
if (isWrapContentWidth) {
if (isLayoutRtl()) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, myWidth - params.mLeft);
} else {
width = Math.max(width, myWidth - params.mLeft + params.leftMargin);
}
} else {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, params.mRight);
} else {
width = Math.max(width, params.mRight + params.rightMargin);
}
}
}
if (isWrapContentHeight) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
height = Math.max(height, params.mBottom);
} else {
height = Math.max(height, params.mBottom + params.bottomMargin);
}
}
if (child != ignore || verticalGravity) {
left = Math.min(left, params.mLeft - params.leftMargin);
top = Math.min(top, params.mTop - params.topMargin);
}
if (child != ignore || horizontalGravity) {
right = Math.max(right, params.mRight + params.rightMargin);
bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
}
}
}
// 先找到最 Top,最 Start 的View 作为 baseline。稍后再使用 RTL 模式
View baselineView = null;
LayoutParams baselineParams = null;
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
if (baselineView == null || baselineParams == null
|| compareLayoutPosition(childParams, baselineParams) < 0) {
baselineView = child;
baselineParams = childParams;
}
}
}
mBaselineView = baselineView;
if (isWrapContentWidth) {
// 宽度中已经包含了左padding,因为它的计算方式就是通过观测每个它右边的子View
width += mPaddingRight;
if (mLayoutParams != null && mLayoutParams.width >= 0) {
width = Math.max(width, mLayoutParams.width);
}
width = Math.max(width, getSuggestedMinimumWidth());
width = resolveSize(width, widthMeasureSpec);
if (offsetHorizontalAxis) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
final int[] rules = params.getRules(layoutDirection);
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
centerHorizontal(child, params, width);
} else if (rules[ALIGN_PARENT_RIGHT] != 0) {
final int childWidth = child.getMeasuredWidth();
params.mLeft = width - mPaddingRight - childWidth;
params.mRight = params.mLeft + childWidth;
}
}
}
}
}
if (isWrapContentHeight) {
// Height already has top padding in it since it was calculated by looking at
// the bottom of each child view
height += mPaddingBottom;
if (mLayoutParams != null && mLayoutParams.height >= 0) {
height = Math.max(height, mLayoutParams.height);
}
height = Math.max(height, getSuggestedMinimumHeight());
height = resolveSize(height, heightMeasureSpec);
if (offsetVerticalAxis) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
final int[] rules = params.getRules(layoutDirection);
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
centerVertical(child, params, height);
} else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
final int childHeight = child.getMeasuredHeight();
params.mTop = height - mPaddingBottom - childHeight;
params.mBottom = params.mTop + childHeight;
}
}
}
}
}
if (horizontalGravity || verticalGravity) {
final Rect selfBounds = mSelfBounds;
selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
height - mPaddingBottom);
final Rect contentBounds = mContentBounds;
Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
layoutDirection);
final int horizontalOffset = contentBounds.left - left;
final int verticalOffset = contentBounds.top - top;
if (horizontalOffset != 0 || verticalOffset != 0) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE && child != ignore) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
if (horizontalGravity) {
params.mLeft += horizontalOffset;
params.mRight += horizontalOffset;
}
if (verticalGravity) {
params.mTop += verticalOffset;
params.mBottom += verticalOffset;
}
}
}
}
}
if (isLayoutRtl()) {
final int offsetWidth = myWidth - width;
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
params.mLeft -= offsetWidth;
params.mRight -= offsetWidth;
}
}
}
setMeasuredDimension(width, height);
}
所以结论就是LinearLayout在使用weight属性进行布局时会对子View进行两次measure
RelativeLayout 会根据2次排列的结果对子View各做一次measure。因为 RelativeLayout 中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和Xml布局中View的顺序不同,在确定每个子View的位置的时候,需要先给所有的子View排序一下。又因为RelativeLayout允许ViewB在横向上依赖ViewA,ViewA在纵向上依赖B。所以需要横向纵向分别进行一次排序测量。
网友评论