Android View 的工作原理
文章主要參考書籍《Android 開發藝術探索》(任玉剛 著),與書籍主要區別:
- 原始碼基於 Android 19,Android 版本變化導致的變化會標註說明
- 追加大量流程圖,原始碼追加更詳細註釋,以方便理解與記憶
- 擴充套件了一些書籍上未說明的原始碼、流程說明
前言
View 的三大流程:測量流程(mesure)、佈局流程(layout)、繪製流程(draw)
目錄
- 一、初識 ViewRootImpl 和 PhoneWindow.DecorView
- 二、理解 View.MeasureSpec
- 小總結
- MeasureSpec 格式
- MeasureSpec 和 ViewGroup.LayoutParams 關係
- MeasureSpec 計算的流程與結果
- DecorView 的 MeasureSpec 計算原始碼分析
- 普通 View 的 MeasureSpec 計算原始碼分析
- 三、View 的 measure 過程
- 小總結
- 相關 API
- 從 DecorView 到普通 View 的 measure 原理分析
- ViewGroup 的 measure 原理分析
- 普通 View 的 measure 原理分析
- 在 Activity 生命週期中獲取測量結果解決方案
- 四、View 的 layout 過程
- 小總結
- 相關 API
- 從 DecorView 到普通 View 的 layout 原理分析
- 普通 View 的 layout 原理分析
- 五、View 的 draw 過程
- 小總結
- 相關 API
- 從 DecorView 到普通 View 的 draw 原理分析
- 普通 View 的 draw 原理分析
- ViewGroup 的 draw 原理分析
一、初識 ViewRootImpl 和 PhoneWindow.DecorView
DecorView 作為頂級 View,它繼承自 FrameLayout,View 層的事件都記過 DecorView,然後才傳遞給 View
ViewRootImpl:View 階級的最高階,View 的三大流程均由它實現,是連線 WindowManager 和 DecorView 的紐帶,主要實現 View 與 WindowManager 之間的協議,可參考 WindowManagerGlobal。ViewRootImpl官方釋義如下:
/** * The top of a view hierarchy, implementing the needed protocol between View * and the WindowManager.This is for the most part an internal implementation * detail of {@link WindowManagerGlobal}. */ public final class ViewRootImpl implements ... 複製程式碼
ActivityThread 中,當 Activity 建立完畢後,會將 DecorView 新增到 Window 中,同時建立 ViewRootImpl 物件,並將 ViewRootImpl 與 DecorView 建立關聯
/** * WindowManagerGlobal 類 */ public void addView(View, ViewGroup.LayoutParams, Display, Window) { ... root = new ViewRootImpl(view.getContext(), display); ... root.setView(view, wparams, panelParentView); ... } 複製程式碼
View 繪製從 ViewRootImpl.performTraversals() 開始,依次完成測量流程(mesure)、佈局流程(layout)、繪製流程(draw)。流程圖如下:

/** * ViewRootImpl 類 */ private void performTraversals() { /** * 引數說明 * mWidth, mHeight: 視窗有效尺寸 * lp: 窗口布局引數 * desiredWindowWidth: 視窗尺寸 * desiredWindowHeight: 視窗尺寸 */ ... // 獲取根 View 的 MeasureSpec,並執行測量流程 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... // 佈局流程 performLayout(lp, desiredWindowWidth, desiredWindowHeight); ... // 繪製流程 performDraw(); ... } 複製程式碼
- 測量過程[performMeasure()]完成後,View 高寬就測量完畢,在幾乎所有情況下,它都等於 View 的最終高寬
- 佈局過程[performLayout()]決定了 View 的四個頂點的座標,以及 View 的實際高寬
- 繪製過程[performDraw()]決定了 View 的內容顯示
二、理解 View.MeasureSpec
小總結
- MeasureSpec 是 Android 系統用來測量 View 的高寬的引數
- 對於 DecorView,其 MeasureSpec 由視窗的尺寸和其自身的 LayoutParams 共同確定
- 對於普通 View,其 MeasureSpec 有父容器的 MeasureSpec 和自身 LayoutParams 共同確定
1. 相關 API
獲取計算結果:
- int getMode(int)
- int getSize(int)
2. MeasureSpec 格式
MeasureSpec 是32位 int 值,高2位表示 SpecMode,低30位表示 SpecSize
- SpecMode:指測量模式,有三類:UNSPECIFIED、EXACTLY、AT_MOST
- SpecSize:值在某個測量模式下的規格大小
- UNSPECIFIED:未註明的,意思是父容器對 View 不做任何限制,要多大給多大,一般用於系統內部
- EXACTLY:精確的,意思是明確 View 大小值, 對應 LayoutParams.MATCH_PARENT 和具體數值 這兩種模式
- AT_MOST:至多的,意思是 View 的大小值根據具體實現,但不能超過該值, 對應 LayoutParams.WRAP_CONTENT
/** * MeasureSpec 類 */ private static final int MODE_SHIFT = 30; private static final int MODE_MASK= 0x3 << MODE_SHIFT; public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY= 1 << MODE_SHIFT; public static final int AT_MOST= 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } 複製程式碼
3. MeasureSpec 和 ViewGroup.LayoutParams 關係
在 View 測量的時候,系統會將 View 的 LayoutParams 根據***父容器所施加的約束***轉換成對應的 MeasureSpec,然後***根據這個 MeasureSpec 來測量 View 的高寬***
4. MeasureSpec 計算的流程與結果
DecorView 與普通 View 的 MeasureSpec 計算流程,分別如下:

DecorView 與普通 View 的 MeasureSpec 計算結果表,分別如下:

5. DecorView 的 MeasureSpec 計算原始碼分析
/** * ViewRootImpl 類 */ /** * @param lp WindowManager.LayoutParams, 窗口布局引數 * @param desiredWindowWidth int, 視窗尺寸 * @param desiredWindowHeight int, 視窗尺寸 */ private boolean measureHierarchy(...) { ... childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... } /** * @param windowSize 視窗有效尺寸 * @param rootDimension 視窗的佈局引數尺寸 * @return 根 View(DecorView) 的 MeasureSpec */ /** * 官方釋義 * @param windowSize The available width or height of the window * @param rootDimension The layout params for one dimension (width or height) of the window * @return The measure spec to use to measure the root view */ private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; } 複製程式碼
6. 普通 View 的 MeasureSpec 計算原始碼分析
/** * ViewGroup 類 */ /** * @param child 子 View * @param parentWidthMeasureSpec 父容器寬度 * @param widthUsed 額外使用尺寸 * @param parentHeightMeasureSpec 父容器高度 * @param heightUsed 額外使用尺寸 */ protected void measureChildWithMargins(View, int, int, int, int) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec( parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec( parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } /** * @param spec 父容器尺寸 * @param padding 補白區尺寸 * @param childDimension View 尺寸 */ public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; case MeasureSpec.AT_MOST: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } 複製程式碼
三、View 的 measure 過程
小總結
- View.measure() 是 final 修飾的,所以無論是 View,ViewGroup 都不無法重寫該方法,但 measure() 會呼叫 onMeasure(),所以可通過重寫 onMeasure(),完成自定義控制元件的測量
- 直接繼承 View 的自定義控制元件需要重寫 onMeasure() 並設定 WRAP_CONTENT 時的高寬,否則該設定相當與 MATCH_PARENT
- ViewGroup 自身是沒有重寫 onMeasure() 的,原因是不同的 ViewGroup 子類是有不同的佈局特性。所以直接繼承 ViewGroup 的自定義控制元件需要重寫 onMeasure()
1. 相關 API
獲取測量結果:
- int getMeasuredWidth()
- int getMeasuredHeight()
測量相關方法:
-
void setMeasuredDimension(int, int)
-
int getPaddingLeft()
-
int getPaddingTop()
-
int getPaddingRight()
-
int getPaddingBottom()
-
int getPaddingStart()
-
int getPaddingEnd()
-
setPadding(int left, int top, int right, int bottom)
-
ViewParent getParent()
2. 從 DecorView 到普通 View 的 measure 原理分析

/** * ViewRootImpl 類 */ private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } /** * View 類 */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... onMeasure(widthMeasureSpec, heightMeasureSpec); ... } /** * ViewGroup 類,見標題【MeasureSpec 和 LayoutParams 關係】 */ protected void measureChildWithMargins(View, int, int, int, int) { ... } 複製程式碼
- measure() 是 final 修飾的,所以無論是 View,ViewGroup 都不無法重寫該方法
3. ViewGroup 的 measure 原理分析

- ViewGroup 自身是沒有重寫 onMeasure() 的,原因是不同的 ViewGroup 子類是有不同的佈局特性
/** * ViewGroup 類 */ /** * @param widthMeasureSpec ViewGroup 寬度 * @param heightMeasureSpec ViewGroup 高度 */ protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } /** * @param child 子 View 的 MeasureSpec * @param parentWidthMeasureSpec ViewGroup 寬度 * @param parentHeightMeasureSpec ViewGroup 高度 */ protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec( parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec( parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } /** * 見標題【2.4. MeasureSpec 計算】 */ protected void measureChildWithMargins(View, int, int, int, int) { ... } 複製程式碼
4. 普通 View 的 measure 原理分析

/** * View 類 */ /** * @param widthMeasureSpec 寬度 MeasureSpec * @param heightMeasureSpec 高度 MeasureSpec */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } /** * @param size 預設大小 * @param measureSpec 父容器約束 * @return View 測量後的 MeasureSpec */ public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; } /** * mBackground: Drawable 物件 * mMinWidth: 對應 android.minWidth 屬性 * getMinimumWidth(): Drawable 物件的原始寬度 */ protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } /** * Drawable 類 */ public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0; } public int getMinimumHeight() { final int intrinsicHeight = getIntrinsicHeight(); return intrinsicHeight > 0 ? intrinsicHeight : 0; } 複製程式碼
5. 在 Activity 生命週期中獲取測量結果解決方案
由於 View 的 measure 過程與 Activity 生命週期不同步,所以在 Activity 生命週期中直接獲取測量結果會有問題,解決方案有以下幾種: 方法一:
@Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } } 複製程式碼
方法二:通過 post() 將訊息投遞到訊息佇列尾部,當 Looper 呼叫該訊息時,View 已經初始化好了
@Override protected void onStart() { super.onStart(); view.post(new Runnable() { @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); } 複製程式碼
方法三:
@Override protected void onStart() { super.onStart(); ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() { @Override public void onGlobalFocusChanged(View oldFocus, View newFocus) { view.getViewTreeObserver().removeOnGlobalFocusChangeListener(this); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); } 複製程式碼
方法四:不推薦,原因是 View 為 MATCH_PARENT 時,不可用
@Override protected void onStart() { super.onStart(); // View 為 WRAP_CONTENT 時 int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST); // 取最大值 int heghtMeasureSpec = widthMeasureSpec; view.measure(widthMeasureSpec, getMinimumHeight()); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); // View 為 dp / px 時,假設高寬依次是100px,200px int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); int heghtMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY); view.measure(widthMeasureSpec, getMinimumHeight()); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } 複製程式碼
四、View 的 layout 過程
小總結
- 與 measure 過程原因類似,直接繼承 ViewGroup 的自定義控制元件需要重寫 onLayout()
1. 相關 API
獲取佈局結果:
- getTop()
- getBottom()
- getLeft()
- getRight()
- getWidth()
- getHeight()
2. 從 DecorView 到普通 View 的 layout 原理分析

/** * ViewRootImpl 類 */ private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; final View host = mView; ... Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mInLayout = false; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { // requestLayout() was called during layout. // If no layout-request flags are set on the requesting views, there is no problem. // If some requests are still pending, then we need to clear those flags and do // a full request/measure/layout pass to handle this situation. ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false); if (validLayoutRequesters != null) { ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ... } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; } /** * View 類 */ /** * 見下述【普通View的layout原理】 */ public void layout(int l, int t, int r, int b) { ... } 複製程式碼
3. 普通 View 的 layout 原理分析

- 無論是 View 還是 ViewGroup 的 onLayout() 都是未定義,所以直接繼承 ViewGroup 的自定義控制元件需要重寫 onLayout()
/** * View 類 */ /** * @param Left/Top:與父容器的相對偏移量 * @param Right/Bottom: measure() 後與父容器的相對偏移量 */ public void layout(int l, int t, int r, int b) { ... // 確定當前 View 的大小與相對父容器的位置 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); // 呼叫 onLayout(...) if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); ... } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; } /** * true: 當前 View(注意此處為父容器)是 ViewGroup,且模式為 LAYOUT_MODE_OPTICAL_BOUNDS * ViewGroup 模式有兩種: * LAYOUT_MODE_CLIP_BOUNDS:預設模式,表示邊界未加工的 * LAYOUT_MODE_OPTICAL_BOUNDS:大體含義是支援特效,如陰影、暖色、冷色 */ public static boolean isLayoutModeOptical(Object o) { return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical(); } /** * 設定 ViewGroup 為 LAYOUT_MODE_OPTICAL_BOUNDS 時,當前 ViewGroup 的位置 */ private boolean setOpticalFrame(int left, int top, int right, int bottom) { ... return setFrame(...); } /** * Assign a size and position to this view. * 指定當前 View 的大小與相對父容器的位置 */ protected boolean setFrame(int left, int top, int right, int bottom) { ... } protected void onLayout(boolean changed, int left, int top, int right, int bottom) { } /** * ViewGroup 類 */ public final void layout(int l, int t, int r, int b) { if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) { if (mTransition != null) { mTransition.layoutChange(this); } super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes mLayoutCalledWhileSuppressed = true; } } protected abstract void onLayout(boolean changed, int l, int t, int r, int b); 複製程式碼
五、View 的 draw 過程
小總結
- 有顯示內容的自定義控制元件需要重寫 onDraw(Canvas),以便繪製自身的顯示內容
1. 相關 API
無
2. 從 DecorView 到普通 View 的 draw 原理分析

- 由於原始碼過於複雜,並且設計到硬體渲染,故上述流程僅供參考,下述貼出部分原始碼,詳細過程見 Android 原始碼
/** * ViewRootImpl 類 */ private void performDraw() { if (!mAttachInfo.mScreenOn && !mReportNextDraw) { return; } final boolean fullRedrawNeeded = mFullRedrawNeeded; mFullRedrawNeeded = false; mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ... } private void draw(boolean fullRedrawNeeded) { ... if (!sFirstDrawComplete) { synchronized (sFirstDrawHandlers) { sFirstDrawComplete = true; final int count = sFirstDrawHandlers.size(); for (int i = 0; i< count; i++) { mHandler.post(sFirstDrawHandlers.get(i)); } } } scrollToRectOrFocus(null, false); ... final Rect dirty = mDirty; ... if (!dirty.isEmpty() || mIsAnimating) { if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) { // Draw with hardware renderer. ... attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty); } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface // for instance) so we should just bail out. Locking the surface with software // rendering at this point would lock it forever and prevent hardware renderer // from doing its job when it comes back. // Before we request a new frame we must however attempt to reinitiliaze the // hardware renderer if it's in requested state. This would happen after an // eglTerminate() for instance. ... if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { return; } } } if (animating) { mFullRedrawNeeded = true; scheduleTraversals(); } } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. Canvas canvas; ... try { ... try { ... mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); } finally { if (!attachInfo.mSetIgnoreDirtyState) { // Only clear the flag if it was not set during the mView.draw() call attachInfo.mIgnoreDirtyState = false; } } } finally { ... } return true; } /** * View 類 */ /** * 見下述【普通View的draw原理】 */ public void draw(Canvas canvas) { ... } 複製程式碼
3. 普通 View 的 draw 原理分析

- 繪製背景:Android 19 採用上述原始碼中的程式碼塊實現。Android 21 則通過 drawBackground(Canvas) 實現
/** * 繪製步驟: * 1. 繪製背景 * 2. 如果需要,在做淡入淡出處理前儲存畫布圖層???(???:不確定,下同) * 3. 繪製 View 內容 * 4. 繪製子 View 的內容 * 5. 如果需要,繪製淡入淡出邊界和恢復圖層??? * 6. 繪製附加內容,如滾輪等 * * 官方釋義: * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ public void draw(Canvas canvas) { // 調整當前畫布的邊界??? if (mClipBounds != null) { canvas.clipRect(mClipBounds); } // dirtyOpaque:true,透明 final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { final Drawable background = mBackground; if (background != null) { final int scrollX = mScrollX; final int scrollY = mScrollY; if (mBackgroundSizeChanged) { background.setBounds(0, 0,mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; } if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // we're done... return; } // 透明佈局處理,貌似做了一些優化??? ... } 複製程式碼
4. ViewGroup 的 draw 原理分析

- 由於該部分涉及的原始碼複雜程度高,故上述流程僅供參考,下述貼出部分原始碼,詳細過程見 Android 原始碼
- 由於該部分涉及的原始碼複雜程度高,所以基本只體現關鍵方法,其他則簡要說明,請對照 Android 原始碼檢視
/** * ViewGroup */ @Override protected void dispatchDraw(Canvas canvas) { final int count = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; // 動畫執行前準備:建立所有子 View 的繪製內容快取、觸發動畫開始事件 if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { ... for (int i = 0; i < count; i++) { final View child = children[i]; ... child.buildDrawingCache(true); } ... if (mAnimationListener != null) { mAnimationListener.onAnimationStart(controller.getAnimation()); } } // 調整 padding:現場保護 int saveCount = 0; final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { saveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } // 繪製子 View 內容,並執行動畫效果 // We will draw our child's animation, let's reset the flag mPrivateFlags &= ~PFLAG_DRAW_ANIMATION; mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED; boolean more = false; final long drawingTime = getDrawingTime(); /* 是否按指定順序繪製子 View 的內容 */ if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) { for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } } else { for (int i = 0; i < count; i++) { final View child = children[getChildDrawingOrder(count, i)]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } } // 繪製隱藏子 View 的內容 // Draw any disappearing views that have animations if (mDisappearingChildren != null) { final ArrayList<View> disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size() - 1; // Go backwards -- we may delete as animations finish for (int i = disappearingCount; i >= 0; i--) { final View child = disappearingChildren.get(i); more |= drawChild(canvas, child, drawingTime); } } ... // 調整 padding:現場還原 if (clipToPadding) { canvas.restoreToCount(saveCount); } // 狀態標識等更新,並觸發動畫結束事件 // mGroupFlags might have been updated by drawChild() flags = mGroupFlags; if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) { invalidate(true); } if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 && mLayoutAnimationController.isDone() && !more) { // We want to erase the drawing cache and notify the listener after the // next frame is drawn because one extra invalidate() is caused by // drawChild() after the animation is over mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER; final Runnable end = new Runnable() { public void run() { notifyAnimationListener(); } }; post(end); } } /** * 繪製 View 內容,並執行動畫效果 */ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } /** * View 類 */ /** * 該方法只能通過 ViewGroup.drawChild() 呼叫 */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { ... // 動畫執行 final Animation a = getAnimation(); if (a != null) { more = drawAnimation(parent, drawingTime, a, scalingRequired); ... } else { ... } ... // 顯示內容準備 DisplayList displayList = null; Bitmap cache = null; boolean hasDisplayList = false; if (caching) { ... hasDisplayList = canHaveDisplayList(); ... cache = getDrawingCache(true); ... } ... displayList = getDisplayList(); ... final boolean hasNoCache = cache == null || hasDisplayList; final boolean offsetForScroll = cache == null && !hasDisplayList && layerType != LAYER_TYPE_HARDWARE; // 畫布調整 ... restoreTo = canvas.save(); ... canvas.translate(mLeft - sx, mTop - sy); ... canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop); ... // 繪製 View 內容 if (hasNoCache) { boolean layerRendered = false; ... if (!layerRendered) { if (!hasDisplayList) { // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); } } else { mPrivateFlags &= ~PFLAG_DIRTY_MASK; ((HardwareCanvas) canvas).drawDisplayList(displayList, null, flags); } } } else if (cache != null) { ... canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint); } // 畫布調整:現場還原 if (restoreTo >= 0) { canvas.restoreToCount(restoreTo); } // 狀態標識等更新,結束動畫效果 if (a != null && !more) { if (!hardwareAccelerated && !a.getFillAfter()) { onSetAlpha(255); } parent.finishAnimatingView(this, a); } if (more && hardwareAccelerated) { if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) { // alpha animations should cause the child to recreate its display list invalidate(true); } } mRecreateDisplayList = false; return more; } 複製程式碼
參考
- 《Android 開發藝術探索》(任玉剛 著) 第四章 View 的工作原理
- Android 19 原始碼(主要)
- Android 21 原始碼
宣告
限於作者水平有限,出錯難免,請積極拍磚! 歡迎任何形式的轉載,轉載請保留本文原文連結: ofollow,noindex">juejin.im/post/5c07a6…
結言
花了 N 久的時間把這篇部落格給寫完了。以後複習或繼續深入理解 View 工作原理也輕鬆了許多。
該篇部落格主要以流程以及原始碼註釋來說明 View 工作原理。論文字描述的詳細程度要比任大大的《Android 開發藝術探索》要簡略許多,比較不容易閱讀。所以還是推薦大家閱讀任大大的《Android 開發藝術探索》。或者檢視[URL](本來想在這裡貼任大大的原創部落格地址,但找不著了,笑哭。)
另外,CSDN 居然無故把我的賬戶給封了,無語啊。沒奈何,只好把部落格搬到稀土掘金上了