layout - 佈局
確定View的最終寬高以及四個頂點的位置!
接着上一篇 View的繪製流程分析之二 – measure 往下分析layout過程!
在ViewRootImpl 中的performTraversals() 函數內部,執行performMeasure() 完畢之後,
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// ...
layoutRequested = true;
- 1
- 2
- 3
- 4
- 5
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight); // 開始佈局過程!
// ...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// ...
final View host = mView;
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// ...
}
} finally {
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
host就是那個DecorView對象!
然後又走到了View中的 layout()方法!
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);
// ...
}
// ...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
在layout() 內部調用了onLayout() 函數!
onLayout() 在View.java中是一個空實現!
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
- 1
- 2
來看ViewGroup中
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
- 1
- 2
- 3
也是空實現,變成了一個抽象方法!!!
其實,不同的ViewGroup對象,有不同的展現形式,所以需要在不同的ViewGroup對象實現各自的onLayout() 函數!
還是那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 */);
}
- 1
- 2
- 3
- 4
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
// 1. 獲取子view的個數
final int count = getChildCount();
// 2. FrameLayout的左側padding值
final int parentLeft = getPaddingLeftWithForeground();
// 3. FrameLayout的右側padding值
final int parentRight = right - left - getPaddingRightWithForeground();
// 4. FrameLayout的頂部padding值
final int parentTop = getPaddingTopWithForeground();
// 5. FrameLayout的底側padding值
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
// 6. 遍歷子view
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 7. 只操作沒有GONE的view
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
// 8. 判斷 FrameLayout橫向的gravity屬性
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL: // 水平居中
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT: // 水平居右
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT: // 水平居左,默認情況!
default:
childLeft = parentLeft + lp.leftMargin;
}
// 9. 判斷 FrameLayout豎向的gravity屬性
switch (verticalGravity) {
case Gravity.TOP: // 居上
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL: // 豎向居中
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM: // 居下
childTop = parentBottom - height - lp.bottomMargin;
break;
default: // 默認居上
childTop = parentTop + lp.topMargin;
}
// 開始子view測量過程
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
註釋已經寫得很詳細了,就不在過多介紹!
child.layout() 是針對一個個子view進行測量!
如果 child是一個viewGroup,則會調用ViewGroup中的layout()函數,然後調用具體ViewGroup子類的onLayout()函數!
如果child是一個View,則會調用View中的layout()函數,然後調用具體View子類的onLayout()函數!
父容器在layout()方法中完成了自己的佈局之後,就通過onLayout()方法去調用子view的layout()方法,然後子view通過自己的layout()函數來完成自己的佈局,這樣一層層往下傳遞就完成了這個View流程樹的layout過程!