首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> C++ 入門

Android View 繪製流程源碼深度解析:onAttach、onMeasure、onLayout、onDraw 完整調用鏈

     編輯:C++ 入門

前言

承接前文《源碼解析:Dialog、PopupWindow 與 Activity 第一個 View 的由來》,本文深入梳理 View 繪製流程中四大核心回調——onAttachedToWindow、onMeasure、onLayout、onDraw——在 ViewRootImpl 中的完整調用鏈與源碼邏輯。

1. 入口:ViewRootImpl.setView()

當 Activity 的 DecorView(實為 FrameLayout)被添加到 WindowManager 後,ViewRootImpl.setView() 被調用,內部調用 requestLayout() → scheduleTraversals() → 發送 DO_TRAVERSAL 消息,最終在 handleMessage() 中觸發 performTraversals(),這是整個繪製流程的核心入口。

2. performTraversals() 關鍵階段拆解

2.1 初始化與 Attach 階段

首次執行時 (mFirst == true),調用 host.dispatchAttachedToWindow(attachInfo, 0),依次觸發:

  1. onAttachedToWindow() —— 僅保證在 onDraw 前調用,不保證在 onMeasure 前後。
  2. OnAttachStateChangeListener.onViewAttachedToWindow()
  3. onWindowVisibilityChanged()

隨後調用 fitSystemWindows() 處理系統窗口邊距(狀態欄、導航欄),並可能再次觸發 requestLayout()。

2.2 Measure 階段

核心邏輯在 performTraversals() 中段,根據 LayoutParams(WRAP_CONTENT、MATCH_PARENT、具體尺寸)計算 MeasureSpec,調用 host.measure()。
View.measure() 內部檢查標志位,最終回調 onMeasure(),並設置 LAYOUT_REQUIRED 標志。

2.3 Layout 階段

若 mLayoutRequested 為真,調用 host.layout(0, 0, measuredWidth, measuredHeight)。
View.layout() → setFrame() 判斷位置是否改變 → 若改變或需佈局,回調 onLayout() 並觸發 OnLayoutChangeListener。
setFrame() 內部會調用 invalidate() 標記髒區域,最終導致重繪。

2.4 Draw 階段

調用 draw(fullRedrawNeeded) → View.draw(canvas),繪製順序:

  1. 繪製背景 (mBGDrawable)
  2. 繪製自身內容 (onDraw(canvas))
  3. 繪製子 View (dispatchDraw(canvas))
  4. 繪製裝飾(滾動條等)

最後通過 WindowSession.finishDrawing() 通知底層繪製完成。

3. 關鍵源碼片段註解

dispatchAttachedToWindow

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
    mWindowAttachCount++;
    onAttachedToWindow();
    // 分發 listener 回調
    for (OnAttachStateChangeListener listener : listeners) {
        listener.onViewAttachedToWindow(this);
    }
    int vis = info.mWindowVisibility;
    if (vis != GONE) onWindowVisibilityChanged(vis);
}

View.measure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT
        || widthMeasureSpec != mOldWidthMeasureSpec
        || heightMeasureSpec != mOldHeightMeasureSpec) {
        mPrivateFlags &= ~MEASURED_DIMENSION_SET;
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("onMeasure() did not set measured dimension");
        }
        mPrivateFlags |= LAYOUT_REQUIRED;
    }
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
}

View.layout() 與 setFrame()

public void layout(int l, int t, int r, int b) {
    boolean changed = setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        mPrivateFlags &= ~LAYOUT_REQUIRED;
    }
    mPrivateFlags &= ~FORCE_LAYOUT;
}

protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = (mLeft != left || mRight != right || mTop != top || mBottom != bottom);
    if (changed) {
        invalidate(sizeChanged);
        // ... 更新 mLeft/mTop/mRight/mBottom 等
    }
    return changed;
}

View.draw()

public void draw(Canvas canvas) {
    // 1. 背景
    if (!dirtyOpaque) background.draw(canvas);
    // 2. 內容
    if (!dirtyOpaque) onDraw(canvas);
    // 3. 子 View
    dispatchDraw(canvas);
    // 4. 裝飾
    onDrawScrollBars(canvas);
}

4. 總結流程圖

setView()
  → requestLayout()
  → scheduleTraversals()
  → performTraversals()
      ├─ dispatchAttachedToWindow() → onAttachedToWindow()
      ├─ fitSystemWindows() → requestLayout()
      ├─ measure() → onMeasure()
      ├─ layout() → setFrame() → onLayout()
      └─ draw() → onDraw() → dispatchDraw()

5. 遺留問題與後續

  • relayoutWindow() 與 computeInternalInsets() 的具體作用待深入分析。
  • WindowSession.finishDrawing() 底層交互機制。
  • 後續將分析輸入事件分發流程(參考《Choreographer 機制》)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved