Android 中 Activity 是做爲應用程序的載體存在,表明着一個完整的用戶界面,提供了一個窗口來繪製各類視圖,當 Activity 啓動時,咱們會經過 setContentView 方法來設置一個內容視圖,這個內容視圖就是用戶看到的界面。canvas
PhoneWindow 是 Android 系統中最基本的窗口系統,每一個 Activity 會建立一個。PhoneWindow 是 Activity 和 View 系統交互的藉口。DecorView 本質上是一個 FrameLayout,是 Activity 中全部 View 的祖先。佈局
當一個應用啓動時,會啓動一個主 Activity,Android 系統會根據 Activity 的佈局來對它進行繪製。繪製會從根視圖 ViewRoot 的 performTraversals() 方法開始,從上到下遍歷整個視圖樹,每一個 View 控制負責繪製本身,而 ViewGroup 還須要負責通知本身的子 View 進行繪製操做。視圖操做的過程能夠分爲三個步驟,分別是測量(Measure)、佈局(Layout)和繪製(Draw)。performTraversals 方法在類 ViewRootImpl 內,其核心代碼以下。ui
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ... // 測量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... // 佈局 performLayout(lp, mWidth, mHeight); ... // 繪製 performDraw();
MeasureSpec 表示的是一個 32 位的整數值,它的高 2 位表示測量模式 SpecMode,低 30 位表示某種測量模式下的規格大小 SpecSize。MeasureSpec 是 View 類的一個靜態內部類,用來講明應該如何測量這個View。
三種測量模式。spa
對 DecorView 而言,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同決定;對於普通的 View,它的 MeasureSpec 由父視圖的 MeasureSpec 和其自己的 LayoutParams 共同決定。rest
Measure 用來計算 View 的實際大小。頁面的測量流程從 performMeasure 方法開始。code
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
具體操做是分發給 ViewGroup 的,由 ViewGroup 在它的 measureChild 方法中傳遞給子 View。ViewGroup 經過遍歷自身全部的子 View,並逐個調用子 View 的 measure 方法實現測量操做。orm
// 遍歷測量 ViewGroup 中全部的 View 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); } } } // 測量某個指定的 View 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); }
View (ViewGroup) 的 Measure 方法,最終的測量是經過回調 onMeasure 方法實現的,這個一般由 View 的特定子類本身實現,能夠經過重寫這個方法實現自定義 View。blog
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... onMeasure(widthMeasureSpec, heightMeasureSpec); .... } // 若是須要自定義測量,子類需重寫這個方法 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } // 若是 View 沒有重寫onMeasure 方法,默認會直接調用 getDefaultSize 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; }
Layout 過程用來肯定 View 在父容器的佈局位置,他是父容器獲取子 View 的位置參數後,調用子 View 的 layout 方法並將位置參數傳入實現的。ViewRootImpl 的 performLayout 代碼以下。開發
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ... }
View 的 layout 方法代碼。get
public void layout(int l, int t, int r, int b) { onLayout(changed, l, t, r, b); } // 空方法,子類若是是 ViewGroup 類型,則重寫這個方法,實現 ViewGroup 中全部 View 控件佈局 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
Draw 操做用來將控件繪製出來,繪製的流程從 performDraw 方法開始。performDraw 方法在類 ViewRootImpl 內,其核心代碼以下。
private void performDraw() { boolean canUseAsync = draw(fullRedrawNeeded); } private boolean draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { return false; } } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { ... mView.draw(canvas); ... }
最終調用到每一個 View 的 draw 方法繪製每一個具體的 View,繪製基本上能夠分爲六個步驟。
public void draw(Canvas canvas) { ... // Step 1, draw the background, if needed if (!dirtyOpaque) { drawBackground(canvas); } ... // Step 2, save the canvas' layers saveCount = canvas.getSaveCount(); ... // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers canvas.drawRect(left, top, right, top + length, p); ... canvas.restoreToCount(saveCount); ... // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); }