Android View 的繪制流程 - 開篇 MeasureSpec
Android View 的繪制流程 01 - 前置流程
Android View 的繪制流程 02 - performMeasure
Android View 的繪制流程 03 - performLayout
Android View 的繪制流程 04 - performDraw
Android View 的繪制流程總結
上一章學習了 Measure 的過程. 這章開始學習 Layout 的流程.
3. ViewRootImpl.performTraversals()
ViewRootImpl.java 1576 行
private void performTraversals() {
...
if (...) {
...
if (...) {
if (...) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
layoutRequested = true;
}
}
} else {
...
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
//擺放
performLayout(lp, mWidth, mHeight);
...
}
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
...
//繪制
performDraw();
} else {
...
}
...
}
從 preformLayout 開始跟進.
mWidth 和 mHeight 是 window (也就是 PhoneWindow) 的高度和寬度.
lp 是 window 的 LayoutParams 值. lp.width 和 lp.height 默認都是 MATCH_PARENT,
進入到 preformLayout ()
?
?
3.1 performLayout()
ViewRootImpl.java 2467行
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
final View host = mView;
...
try {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
} ...
}
代碼中把 mView 賦值給了 host, (mView = DecroView)
然后調用 decorView 的 layout 方法.
由于 decorView 是一個容器(FrameLayout),是 ViewGroup 的子類,所以跟蹤代碼的時候,實際上是先進入到 ViewGroup 類中的layout方法中
?
?
3.2 ViewGroup.layout()
ViewGroup.java 6048行
@Override
public final void layout(int l, int t, int r, int b) {
...
super.layout(l, t, r, b);
...
}
看到是一個 final 類型的方法, 說明 ViewGroup 的子類都無法重寫這個方法.
接著調用了 super.layout 方法. ViewGroup 的父類是 View. 接下來進入 View 類中
?
?
3.3 View.layout()
View.java 19571行
public void layout(int l, int t, int r, int b) {
...
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
...
if (...) {
onLayout(changed, l, t, r, b);
...
}
...
}
官方對這個方法的翻譯為
為視圖及其所有子視圖分配大小和位置
這是布局機制的第二階段, (首先是測量). 在這個階段, 每一個父布局都會調用它所有子布局的 layout 來定位它們. 通常是使用在測量階段存儲的 child 測量值.
派生類不應該重寫該方法. 有子 view 的派生類(也就是容器類,父布局)應該重寫 onLayout 方法。在重寫的 onLayout 方法中,它們應該為每一個子 view 的 layout 方法.
參數依次為:Left、Top、Right、Bottom四個點相對父布局的位置。
注:
setFrame(l, t, r, b) 可以理解為給 mLeft , mTop, mRight, mBottom 這四個變量賦值, 然后基本就能確定當前 View 在父視圖的位置了. 這幾個值構成的矩形區域就是當前 View 顯示的位置,這里的具體位置都是相對與父視圖的位置。
接著調用了 onLayout 方法. 這和上一章我們學習的 measure 調用 onMeasure 好像有點類似. 跟進去.
?
?
3.4 View.onLayout()
View.java 19631行
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
首先官方對這個方法的翻譯為:
當該 View 要分配尺寸和位置給它的每一個子 View 時, 該方法會從 layout 方法中被調用.
帶有子 View 的派生類 ( 就是上面說的容器, 父布局 ) 應該覆蓋此方法,并在每個子 View 調用 layout。
看到這是一個空方法. 對于 View 來說, onLayout 只是一個空實現, 一般情況下我們自定義 View 的時候, 也不需要重載該方法.
但是對于 ViewGroup 來說是有用的 ,因為 layout 擺放過程就是父容器布局子 View 的過程. 所以說如果當前 View 是一個容器, 那么流程就會進入到 ViewGroup 的 onLayout 方法中.
但是發現 ViewGroup 中 onLayout 方法只是一個抽象類. 要求子類必須重寫 onLayout 函數. 我們直接進入到 DecorView 中看 onLayout()
?
?
3.5 DecorView.onLayout()
DecorView.java 757行
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
...
}
直接調用了父類的 onLayout. 因為 DecorView 繼承自 FrameLayout. 所以繼續跟進到 FrameLayout 的 onLayout 方法中.
?
?
3.6 FrameLayout.onLayout
FrameLayout.java 260行
@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) {
...
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
看到這里是不是覺得和 onMeasure 有點相似了. 同樣的循環子 View. 不同的是這里循環子 View 調用的是子 View 的 layout 方法.
那么知道這個時候 View 是 DecorView, 他的子 View 是一個 LinearLayout, 也是一個父布局/容器. 那么就又會調用步驟 3.2, 接著調用 步驟3.3 然后在步驟 3.3 中調用 setFrame() 方法 擺放好自己. 接著就會調用 LinearLayout 的 onLayout 方法,擺放內部的子 View
.... 這樣遞歸下去.
我們如果弄懂了上一章中 onMeasure 的流程, 那么這個 onLayout 就很容易理解了.
onMeasure 是遞歸到最后一層, 才開始測量, 然后一層一層的向上傳遞, 測量父 View.
onLayout 同樣也是遞歸, 但是無論是 View, 還是 ViewGroup 都會先把自己擺放布局好, 才去擺放布局子 View .
?
?
其實理解了 onMeasure 流程后, 再來看 onLayout 流程, 就會覺得非常簡單了, 還是建議大家先去看上一章的 onMeasure 流程.
好了, 關于 onLayout 就寫到這里. 寫一章開始學習 繪制的流程..