姓名:李昕洲? ? ? 學(xué)號(hào):16030120026
轉(zhuǎn)載自:http://blog.csdn.net/gongxiaoou/article/details/78806188
【嵌牛導(dǎo)讀】:大家或多或少了解過View,本文將為你揭曉View的工作原理之layout過程。
【嵌牛鼻子】:View樹、layout過程。
【嵌牛提問】:View樹從上到下的布局過程如何? getMeasuredWidth和getWidth的本質(zhì)區(qū)別是什么?
【嵌牛正文】:
layout和onLayout方法的作用
layout用來確定View自己的位置,onLayout用來確定各個(gè)子View的位置
在View類中只有l(wèi)ayout的實(shí)現(xiàn),沒有onLayout的實(shí)現(xiàn),因?yàn)椴煌膶?shí)現(xiàn)類有不同特殊情況。
如下為View類中的onLayout
view sourceprint?
/**
? ? *布置子類
? ? * Called from layout when this view should
? ? * assign a size and position to each of its children.
? ? *
? ? * Derived classes with children should override
? ? * this method and call layout on each of
? ? * their children.
? ? * @param changed This is a new size or position for this view
? ? * 相對(duì)于父控件的左,上,右,下值
? ? * @param left Left position, relative to parent
? ? * @param top Top position, relative to parent
? ? * @param right Right position, relative to parent
? ? * @param bottom Bottom position, relative to parent
? ? */
? ? protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
? ? }
下面是layout方法,這里我們只關(guān)心我們要的代碼其他的省略。
@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);
? ? ? ? if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
? ? ? ? ? ? onLayout(changed, l, t, r, b);
? ? ? ? ? ? ...省略代碼...
? ? ? ? }
? ? }
layout流程大致如下:首先通過setFrame設(shè)置View的四個(gè)頂點(diǎn)在父View的位置,那么此View的位置就確定了;然后調(diào)用onLayout方法確定各個(gè)子View的位置。
下面是setFrame方法(看注釋部分即可):
protected boolean setFrame(int left, int top, int right, int bottom) {
? ? ? boolean changed = false;
? ? ? if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
? ? ? ? ? //位置發(fā)生了變化
? ? ? ? ? changed = true;
? ? ? ? ? // Remember our drawn bit
? ? ? ? ? int drawn = mPrivateFlags & PFLAG_DRAWN;
? ? ? ? ? int oldWidth = mRight - mLeft;
? ? ? ? ? int oldHeight = mBottom - mTop;
? ? ? ? ? int newWidth = right - left;
? ? ? ? ? int newHeight = bottom - top;
? ? ? ? ? //判斷尺寸是否發(fā)生了變化
? ? ? ? ? boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
? ? ? ? ? // Invalidate our old position
? ? ? ? ? invalidate(sizeChanged);
? ? ? ? ? mLeft = left;
? ? ? ? ? mTop = top;
? ? ? ? ? mRight = right;
? ? ? ? ? mBottom = bottom;
? ? ? ? ? mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
? ? ? ? ? mPrivateFlags |= PFLAG_HAS_BOUNDS;
? ? ? ? ? if (sizeChanged) {
? ? ? ? ? ? ? //改變尺寸,但是我們發(fā)現(xiàn)它的具體操作也是交給了子類根據(jù)具體情況實(shí)現(xiàn)
? ? ? ? ? ? ? sizeChange(newWidth, newHeight, oldWidth, oldHeight);
? ? ? ? ? }
? ? ? ? ? if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
? ? ? ? ? ? ? // If we are visible, force the DRAWN bit to on so that
? ? ? ? ? ? ? // this invalidate will go through (at least to our parent).
? ? ? ? ? ? ? // This is because someone may have invalidated this view
? ? ? ? ? ? ? // before this call to setFrame came in, thereby clearing
? ? ? ? ? ? ? // the DRAWN bit.
? ? ? ? ? ? ? mPrivateFlags |= PFLAG_DRAWN;
? ? ? ? ? ? ? invalidate(sizeChanged);
? ? ? ? ? ? ? // parent display list may need to be recreated based on a change in the bounds
? ? ? ? ? ? ? // of any child
? ? ? ? ? ? ? invalidateParentCaches();
? ? ? ? ? }
? ? ? ? ? // Reset drawn bit to original value (invalidate turns it off)
? ? ? ? ? mPrivateFlags |= drawn;
? ? ? ? ? mBackgroundSizeChanged = true;
? ? ? ? ? if (mForegroundInfo != null) {
? ? ? ? ? ? ? mForegroundInfo.mBoundsChanged = true;
? ? ? ? ? }
? ? ? ? ? notifySubtreeAccessibilityStateChangedIfNeeded();
? ? ? }
? ? ? return changed;
? }
? ? ? ? ? // Remember our drawn bit
? ? ? ? ? int drawn = mPrivateFlags & PFLAG_DRAWN;
? ? ? ? ? int oldWidth = mRight - mLeft;
? ? ? ? ? int oldHeight = mBottom - mTop;
? ? ? ? ? int newWidth = right - left;
? ? ? ? ? int newHeight = bottom - top;
? ? ? ? ? //判斷尺寸是否發(fā)生了變化
? ? ? ? ? boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
? ? ? ? ? // Invalidate our old position
? ? ? ? ? invalidate(sizeChanged);
? ? ? ? ? mLeft = left;
? ? ? ? ? mTop = top;
? ? ? ? ? mRight = right;
? ? ? ? ? mBottom = bottom;
? ? ? ? ? mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
? ? ? ? ? mPrivateFlags |= PFLAG_HAS_BOUNDS;
? ? ? ? ? if (sizeChanged) {
? ? ? ? ? ? ? //改變尺寸,但是我們發(fā)現(xiàn)它的具體操作也是交給了子類根據(jù)具體情況實(shí)現(xiàn)
? ? ? ? ? ? ? sizeChange(newWidth, newHeight, oldWidth, oldHeight);
? ? ? ? ? }
? ? ? ? ? if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
? ? ? ? ? ? ? // If we are visible, force the DRAWN bit to on so that
? ? ? ? ? ? ? // this invalidate will go through (at least to our parent).
? ? ? ? ? ? ? // This is because someone may have invalidated this view
? ? ? ? ? ? ? // before this call to setFrame came in, thereby clearing
? ? ? ? ? ? ? // the DRAWN bit.
? ? ? ? ? ? ? mPrivateFlags |= PFLAG_DRAWN;
? ? ? ? ? ? ? invalidate(sizeChanged);
? ? ? ? ? ? ? // parent display list may need to be recreated based on a change in the bounds
? ? ? ? ? ? ? // of any child
? ? ? ? ? ? ? invalidateParentCaches();
? ? ? ? ? }
? ? ? ? ? // Reset drawn bit to original value (invalidate turns it off)
? ? ? ? ? mPrivateFlags |= drawn;
? ? ? ? ? mBackgroundSizeChanged = true;
? ? ? ? ? if (mForegroundInfo != null) {
? ? ? ? ? ? ? mForegroundInfo.mBoundsChanged = true;
? ? ? ? ? }
? ? ? ? ? notifySubtreeAccessibilityStateChangedIfNeeded();
? ? ? }
? ? ? return changed;
? }
下面以LinearLayout為例子來分析onLayout方法。
protected void onLayout(boolean changed, int l, int t, int r, int b) {
? ? ? ? if (mOrientation == VERTICAL) {
? ? ? ? ? ? layoutVertical(l, t, r, b);
? ? ? ? } else {
? ? ? ? ? ? layoutHorizontal(l, t, r, b);
? ? ? ? }
? ? }
這里我們以mOrientation == VERTICAL為例分析。
void layoutVertical(int left, int top, int right, int bottom) {
? ? ? ? final int paddingLeft = mPaddingLeft;
? ? ? ? int childTop;
? ? ? ? int childLeft;
? ? ? ? // 父View的寬度
? ? ? ? final int width = right - left;
? ? ? ? //得到所有子View的最右邊界
? ? ? ? int childRight = width - mPaddingRight;
? ? ? ? //所有子View占用的橫向空間
? ? ? ? int childSpace = width - paddingLeft - mPaddingRight;
? ? ? ? final int count = getVirtualChildCount();
? ? ? ? final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
? ? ? ? final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
? ? ? ? //根據(jù)子View在父View中的Gravity(上,下,左,右,中)來計(jì)算子所有View的上邊界
? ? ? ? switch (majorGravity) {
? ? ? ? ? case Gravity.BOTTOM:
? ? ? ? ? ? ? // mTotalLength contains the padding already
? ? ? ? ? ? ? childTop = mPaddingTop + bottom - top - mTotalLength;
? ? ? ? ? ? ? break;
? ? ? ? ? ? ? // mTotalLength contains the padding already
? ? ? ? ? case Gravity.CENTER_VERTICAL:
? ? ? ? ? ? ? childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
? ? ? ? ? ? ? break;
? ? ? ? ? case Gravity.TOP:
? ? ? ? ? default:
? ? ? ? ? ? ? childTop = mPaddingTop;
? ? ? ? ? ? ? break;
? ? ? ? }
? ? ? ? //重點(diǎn)
? ? ? ? for (int i = 0; i < count; i++) {
? ? ? ? ? ? final View child = getVirtualChildAt(i);
? ? ? ? ? ? if (child == null) {
? ? ? ? ? ? ? ? childTop += measureNullChild(i);
? ? ? ? ? ? } else if (child.getVisibility() != GONE) {
? ? ? ? ? ? ? ? //獲取單個(gè)子View的測試寬高
? ? ? ? ? ? ? ? final int childWidth = child.getMeasuredWidth();
? ? ? ? ? ? ? ? final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
? ? ? ? ? ? ? ? ? ? ? ? (LinearLayout.LayoutParams) child.getLayoutParams();
? ? ? ? ? ? ? ? int gravity = lp.gravity;
? ? ? ? ? ? ? ? if (gravity < 0) {
? ? ? ? ? ? ? ? ? ? gravity = minorGravity;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? final int layoutDirection = getLayoutDirection();
? ? ? ? ? ? ? ? final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
? ? ? ? ? ? ? ? //獲取每個(gè)子View的左邊界
? ? ? ? ? ? ? ? switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
? ? ? ? ? ? ? ? ? ? case Gravity.CENTER_HORIZONTAL:
? ? ? ? ? ? ? ? ? ? ? ? childLeft = paddingLeft + ((childSpace - childWidth) / 2)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? + lp.leftMargin - lp.rightMargin;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? case Gravity.RIGHT:
? ? ? ? ? ? ? ? ? ? ? ? childLeft = childRight - childWidth - lp.rightMargin;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? case Gravity.LEFT:
? ? ? ? ? ? ? ? ? ? default:
? ? ? ? ? ? ? ? ? ? ? ? childLeft = paddingLeft + lp.leftMargin;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //逐個(gè)累計(jì)各個(gè)子View的豎直方向占用空間為布置下一個(gè)子View做準(zhǔn)備
? ? ? ? ? ? ? ? if (hasDividerBeforeChildAt(i)) {
? ? ? ? ? ? ? ? ? ? childTop += mDividerHeight;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? childTop += lp.topMargin;
? ? ? ? ? ? ? ? //最終讓每個(gè)子View各自完成自己的layout
? ? ? ? ? ? ? ? setChildFrame(child, childLeft, childTop + getLocationOffset(child),
? ? ? ? ? ? ? ? ? ? ? ? childWidth, childHeight);
? ? ? ? ? ? ? ? childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
? ? ? ? ? ? ? ? i += getChildrenSkipCount(child, i);
? ? ? ? ? ? }
? ? ? ? }
? ? }
下面是setChildFrame方法,其實(shí)就是讓每個(gè)子View完成自己的layout。
private void setChildFrame(View child, int left, int top, int width, int height) {? ? ? ?
? ? ? ? child.layout(left, top, left + width, top + height);
}
上面基本就將整個(gè)View樹的layout展示了一下。
下面我們來解釋getMeasuredWidth和getWidth的本質(zhì)區(qū)別(高度方向原理一樣)
先看layoutHorizontal—->setChildFrame
void layoutHorizontal(int left, int top, int right, int bottom) {
? ? ...省略代碼...
? ? final int childWidth = child.getMeasuredWidth();
? ? final int childHeight = child.getMeasuredHeight();
? ? setChildFrame(child, childLeft, childTop + getLocationOffset(child),
? ? ? ? ? ? ? ? ? ? ? ? ? ? childWidth, childHeight);
? ? ...省略代碼...
}
private void setChildFrame(View child, int left, int top, int width, int height) {? ? ? ?
? ? ? ? child.layout(left, top, left + width, top + height);
}
從中我們發(fā)現(xiàn)父View給子View布置的寬高(childWidth, childHeight)就是它的測量寬高getMeasuredWidth(),getMeasuredHeight()。
再看layout和setFrame方法和getWidth和getHeight
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);
? ? ...省略代碼...
}
protected boolean setFrame(int left, int top, int right, int bottom) {
? ? ...省略代碼...
? ? mLeft = left;
? ? mTop = top;
? ? mRight = right;
? ? mBottom = bottom;
? ? ...省略代碼...
}
public final int getWidth() {
? ? ? ? return mRight - mLeft;
}
public final int getHeight() {
? ? ? ? return mBottom - mTop;
}
從上面可以看出getMeasuredWidth和getWidth其實(shí)值是一樣的,只是獲取的時(shí)間點(diǎn)不同,measuredWidth(測量寬度)形成于View的measure過程中,而View的width(真實(shí)寬度)形成于layout過程中。
補(bǔ)充說明:我們可以撐的沒事重寫layout如下,這會(huì)造成無法正常顯示等錯(cuò)誤,這只是為了證明可以讓測量寬/高度不等于最終寬/高度。
public void layout(int l, int t, int r, int b) {
? ? super.layout(l, t, r+10, b+10);
}
而且有的View需要多次measure過程,那么在這個(gè)過程中測量寬/高度不等于最終寬/高度,但是最終測量寬/高度等于最終寬/高度public void layout(int l, int t, int r, int b) {
? ? super.layout(l, t, r+10, b+10);
}
而且有的View需要多次measure過程,那么在這個(gè)過程中測量寬/高度不等于最終寬/高度,但是最終測量寬/高度等于最終寬/高度