View的工作原理之layout過程

姓名:李昕洲? ? ? 學(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è)過程中測量寬/高度不等于最終寬/高度,但是最終測量寬/高度等于最終寬/高度


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容