根據 measure 測量出來的寬高,肯定全部 View 的位置。java
View 自己的位置是經過它的四個點來控制的: ide
如下涉及到源碼的部分都是版本27的,爲方便理解觀看,代碼有所刪減。源碼分析
先經過 measure 測量出 ViewGroup 寬高,ViewGroup 再經過 layout 方法根據自身寬高來肯定自身位置。當 ViewGroup 的位置被肯定後,就開始在 onLayout 方法中調用子元素的 layout 方法肯定子元素的位置。子元素若是是 ViewGroup 的子類,又開始執行 onLayout,如此循環往復,直到全部子元素的位置都被肯定,整個 View 樹的 layout 過程就執行完了。佈局
在上一節 《View的繪製-measure流程詳解》中說過,View 的繪製流程是從 ViewRootViewImpl 中的 performMeasure()
、performLayout
、performDraw
開始的。在執行完 performMeasure()
後,開始執行 performLayout
方法:(如下源碼有所刪減)post
//ViewRootViewImpl 類
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
final View host = mView;
/*代碼省略*/
//開始執行 View 的 layout 方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
/*代碼省略*/
}
複製代碼
如此,就執行了 View 的 layout 方法:ui
//View 類
public void layout(int l, int t, int r, int b) {
/*代碼省略*/
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//肯定 View 的四個點後,調用 setOpticalFrame/setFrame 方法來控制 View 位置。
//方法 1--->setOpticalFrame
//方法 2--->setFrame
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//執行 onLayout 方法
onLayout(changed, l, t, r, b);
/*代碼省略*/
}
/*代碼省略*/
}
//方法 1---->setOpticalFrame
//View 類
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
//內部最終也是調用 setFrame 方法
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
//方法 2--->setFrame
//View 類
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
/*代碼省略*/
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
//控件的大小和位置有沒有改變
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
//對 View 的四個點賦值
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
//這裏 sizeChange 方法內部調用了 onSizeChanged 方法。
//因此當控件的大小和位置改變的時候會回調 onSizeChanged 方法
//方法 3--->sizeChange
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
/*代碼省略*/
}
return changed;
}
//方法 3--->sizeChange
// View 類
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
//執行 onSizeChanged 方法
onSizeChanged (newWidth, newHeight, oldWidth, oldHeight);
if (mOverlay != null) {
mOverlay.getOverlayView().setRight(newWidth);
mOverlay.getOverlayView().setBottom(newHeight);
}
rebuildOutline();
}
複製代碼
接下來咱們再看 onLayout
方法,在 View 中找到 onLayout
方法,會發現這是一個空實現的方法,裏面什麼也沒有執行,那麼咱們就在 ViewGroup 中找 onLayout
的實現,發現只是一個抽象方法。spa
//View 類
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
複製代碼
//ViewGroup 類
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
複製代碼
在 View 類中 onLayout 是一個空實現不難理解,由於若是一個控件繼承了 View ,它是沒有子元素的,不須要肯定子元素的位置,只須要肯定本身的位置就夠了。.net
在 ViewGroup 中是一個抽象方法,意思也很明顯了,在控件繼承自 ViewGroup 的時候,咱們必須重寫 onLayout
方法。由於如LinearLayout
、RelativeLayout
,他們的佈局特性都是不同的,須要各自根據本身的特性來進行制定肯定子元素位置的規則。code
下面以 LinearLayout
爲例,分析 onLayout 裏面的邏輯。orm
//LinearLayout 類
@Override
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);
}
}
複製代碼
layoutVertical
和 layoutHorizontal
執行流程相似,就分析layoutVertical
//LinearLayout 類
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
/*省略代碼*/
for (int i = 0; i < count; i++) {
//遍歷子 View
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//獲取子元素的寬度
final int childWidth = child.getMeasuredWidth();
//獲取子元素的高度
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
//加上 子元素的 topMargin 屬性值
childTop += lp.topMargin;
//設置子元素的 位置
//方法 1 ----->setChildFrame
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
/*加上子元素的高度、bottomMargin、偏移量,就是下一個子元素的 初始 top。 如此,子元素就從上到下排列,符合咱們所知的 LinearLayout 的特性*/
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
//方法 1 ----->setChildFrame
//LinearLayout 類
private void setChildFrame(View child, int left, int top, int width, int height) {
//最終又調用了子元素的 layout 方法.
child.layout(left, top, left + width, top + height);
}
複製代碼
能夠看到,在遍歷子元素後,又調用了子元素的 layout 方法。子元素若是是繼承 ViewGroup,仍是會調用到子元素的 onLayout 方法,遍歷本身的子元素,調用本身子元素的 layout 方法,如此循環遞歸,就完成了整個 View 樹的 layout 流程。
getWidth 獲取的值和 getMeasureWidth 獲取的值有什麼不一樣嗎? 首先看 getWidth 源碼:
//View 類
public final int getWidth() {
return mRight - mLeft;
}
複製代碼
而後再看 mRight 和 mLeft 賦值(在咱們分析 layout 的時就有展示):
如下爲超精簡代碼,哈哈:
//View 類
public void layout(int l, int t, int r, int b) {
//setOpticalFrame 最終也是調用了 setFrame 方法
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
}
//View 類
protected boolean setFrame(int left, int top, int right, int bottom) {
//對四個值進行賦值
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
複製代碼
而後咱們再回顧調用 layout 的方法:
//LinearLayout 類
void layoutVertical(int left, int top, int right, int bottom) {
for (int i = 0; i < count; i++) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//將獲取的 childWidth 和 childHeight 傳入進去
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
}
}
//LinearLayout 類
private void setChildFrame(View child, int left, int top, int width, int height) {
//子元素 layout 的方法中傳入的 right = left + width
//子元素 layout 的方法中傳入的 bottom = left + height
child.layout(left, top, left + width, top + height);
}
複製代碼
這下就一目瞭然了,在 getWidth()
方法中 mRight - mLeft
其實就是等於 childWidth
,也就是 getWidth() = getMeasureWidth()
。
那麼,他們兩個有不相等的時候嗎?有的:
//自定義 View
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//手動改變傳入的值
super.onLayout(changed, left, top, right+100, bottom+100);
}
複製代碼
若是咱們在自定義 View 中重寫 onLayout 方法,並手動改變傳入的值, getWidth()
和 getMeasureWidth()
的值天然就不同了。不過這樣作好像沒什麼意義?
還有一種狀況就是在某些狀況下,View 須要屢次 measure 才能肯定本身的測量寬/高,在前幾回測量的時候,其得出的測量寬/高 ( getMeasureWidth()/ getMeasureHeight()) 和最終寬/高 ( getWidth()/ getHeight()) 可能不一致,可是最終他們的值仍是相等的。(這段話摘自剛哥的《Android 開發藝術探索》)
getHeight 和 getMeasuredHeight 過程相似,這裏就不分析了。
因此咱們在平常開發中,咱們能夠認爲二者就是相等的。
另:可能仍是會有疑惑,這裏只分析了 LinearLayout,那麼其餘佈局也適用這個結論麼?
FrameLayout 類
//FrameLayout 類
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
//從這裏看出 FrameLayout 也是適用這個結論的
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
複製代碼
RelativeLayout 類
//RelativeLayout 類
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
//Relativelayout 這裏傳入的是 LayoutParams 中的屬性
//st.mRight 和 st.mBottom 賦值很複雜,不過他們也是適用這個結論的,具體能夠查看源碼分析
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
複製代碼
其餘咱們平常開發使用的佈局也是適用於這個結論的。
《Android開發藝術探索》第四章-View的工做原理