上一篇中咱們講到了Android的觸摸事件傳遞機制,除此以外,關於Android View的繪製流程這一塊也是View相關的核心知識點。咱們都知道,PhoneWindow是Android系統中最基本的窗口系統,每一個Activity會建立一個。同時,PhoneWindow也是Activity和View系統交互的接口。DecorView本質上是一個FrameLayout,是Activity中全部View的祖先。java
從Activity的startActivity開始,最終調用到ActivityThread的handleLaunchActivity方法來建立Activity,相關核心代碼以下:android
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { .... // 建立Activity,會調用Activity的onCreate方法 // 從而完成DecorView的建立 Activity a = performLaunchActivity(r, customIntent); if (a != null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; handleResumeActivity(r.tolen, false, r.isForward, !r.activity..mFinished && !r.startsNotResumed); } } final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { unscheduleGcIdler(); mSomeActivitiesChanged = true; // 調用Activity的onResume方法 ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; ... if (r.window == null &&& !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); // 獲得DecorView View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); // 獲得了WindowManager,WindowManager是一個接口 // 而且繼承了接口ViewManager ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; // WindowManager的實現類是WindowManagerImpl, // 因此實際調用的是WindowManagerImpl的addView方法 wm.addView(decor, l); } } } } public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); ... @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } ... }
在瞭解View繪製的總體流程以前,咱們必須先了解下ViewRoot和DecorView的概念。ViewRoot對應於ViewRootImpl類,它是鏈接WindowManager和DecorView的紐帶,View的三大流程均是經過ViewRoot來完成的。在ActivityThread中,當Activity對象被建立完畢後,會將DecorView添加到Window中,同時會建立ViewRootImpl對象,並將ViewRootImpl對象和DecorView創建關聯,相關源碼以下所示:git
// WindowManagerGlobal的addView方法 public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; View pannelParentView = null; synchronized (mLock) { ... // 建立ViewRootImpl實例 root = new ViewRootImpl(view..getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } try { // 把DecorView加載到Window中 root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }
繪製會從根視圖ViewRoot的performTraversals()方法開始,從上到下遍歷整個視圖樹,每一個View控件負責繪製本身,而ViewGroup還須要負責通知本身的子View進行繪製操做。performTraversals()的核心代碼以下。github
private void performTraversals() { ... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ... //執行測量流程 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... //執行佈局流程 performLayout(lp, desiredWindowWidth, desiredWindowHeight); ... //執行繪製流程 performDraw(); }
performTraversals的大體工做流程圖以下所示:json
顯示不出來的可點擊這裏查看canvas
注意:微信
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
content.getChildAt(0);
MeasureSpec表示的是一個32位的整形值,它的高2位表示測量模式SpecMode,低30位表示某種測量模式下的規格大小SpecSize。MeasureSpec是View類的一個靜態內部類,用來講明應該如何測量這個View。MeasureSpec的核心代碼以下。app
public static class 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; // 精確測量模式,視圖寬高指定爲match_parent或具體數值時生效, // 表示父視圖已經決定了子視圖的精確大小,這種模式下View的測量 // 值就是SpecSize的值。 public static final int EXACTLY = 1 << MODE_SHIFT; // 最大值測量模式,當視圖的寬高指定爲wrap_content時生效,此時 // 子視圖的尺寸能夠是不超過父視圖容許的最大尺寸的任何尺寸。 public static final int AT_MOST = 2 << MODE_SHIFT; // 根據指定的大小和模式建立一個MeasureSpec public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } // 微調某個MeasureSpec的大小 static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. return make MeasureSpec(0, UNSPECIFIED); } int size = getSize(measureSpec) + delta; if (size < 0) { size = 0; } return makeMeasureSpec(size, mode); } }
MeasureSpec經過將SpecMode和SpecSize打包成一個int值來避免過多的對象內存分配,爲了方便操做,其提供了打包和解包的方法,打包方法爲上述源碼中的makeMeasureSpec,解包方法源碼以下:ide
public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }
//desiredWindowWidth和desiredWindowHeight是屏幕的尺寸 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); private static int getRootMeaureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATRCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
// ViewGroup的measureChildWithMargins方法 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 子元素的MeasureSpec的建立與父容器的MeasureSpec和子元素自己 // 的LayoutParams有關,此外還和View的margin及padding有關 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); } public static int getChildMeasureSpec(int spec, int padding, int childDimesion) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); // padding是指父容器中已佔用的空間大小,所以子元素可用的 // 大小爲父容器的尺寸減去padding int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (sepcMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimesion == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... // find out how big it should be resultSize = 0; resultMode == MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
普通View的MeasureSpec的建立規則以下:oop
注意:UNSPECIFIED模式主要用於系統內部屢次Measure的情形,通常不需關注。
結論:對於DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同決定;對於普通的View,它的MeasureSpec由父視圖的MeasureSpec和其自身的LayoutParams共同決定。
由前面的分析可知,頁面的測量流程是從performMeasure方法開始的,相關的核心代碼流程以下。
private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { ... // 具體的測量操做分發給ViewGroup mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ... } // 在ViewGroup中的measureChildren()方法中遍歷測量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]; // 當View的可見性處於GONE狀態時,不對其進行測量 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(); // 根據父容器的MeasureSpec和子View的LayoutParams等信息計算 // 子View的MeasureSpec final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } // View的measure方法 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... // ViewGroup沒有定義測量的具體過程,由於ViewGroup是一個 // 抽象類,其測量過程的onMeasure方法須要各個子類去實現 onMeasure(widthMeasureSpec, heightMeasureSpec); ... } // 不一樣的ViewGroup子類有不一樣的佈局特性,這致使它們的測量細節各不相同,若是須要自定義測量過程,則子類能夠重寫這個方法 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // setMeasureDimension方法用於設置View的測量寬高 setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } // 若是View沒有重寫onMeasure方法,則會默認調用getDefaultSize來得到View的寬高 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 = sepcSize; break; } return result; }
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinmumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0; }
若是View沒有設置背景,那麼返回android:minWidth這個屬性所指定的值,這個值能夠爲0;若是View設置了背景,則返回android:minWidth和背景的最小寬度這二者中的最大值。
直接繼承View的控件須要重寫onMeasure方法並設置wrap_content時的自身大小,不然在佈局中使用wrap_content就至關於使用match_parent。解決方式以下:
protected void onMeasure(int widthMeasureSpec, int height MeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widtuhSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); // 在wrap_content的狀況下指定內部寬/高(mWidth和mHeight) int heightSpecSize = MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(widthSpecSize, mHeight); } }
protected void onMeasure(int widthMeasureSpec, int hegithMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } } // measureVertical核心源碼 // See how tall everyone is. Also remember max width. for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); ... // Determine how big this child would like to be. If this or // previous children have given a weight, then we allow it to // use all available space (and we will shrink things later // if need) measureChildBeforeLayout( child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); if (oldHeight != Integer.MIN_VALUE) { lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); }
系統會遍歷子元素並對每一個子元素執行measureChildBeforeLayout方法,這個方法內部會調用子元素的measure方法,這樣各個子元素就開始依次進入measure過程,而且系統會經過mTotalLength這個變量來存儲LinearLayout在豎直方向的初步高度。每測量一個子元素,mTotalLength就會增長,增長的部分主要包括了子元素的高度以及子元素在豎直方向上的margin等。
// LinearLayout測量本身大小的核心源碼 // Add in our padding mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; // Check against our minimum height heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // Reconcile our calculated size with the heightMeasureSpec int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); heightSize = heightSizeAndState & MEASURED_SIZE_MASK; ... setMeasuredDimension(resolveSizeAndSize(maxWidth, widthMeasureSpec, childState), heightSizeAndState); public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { 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: // 高度不能超過父容器的剩餘空間 if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
因爲View的measure過程和Activity的生命週期方法不是同步執行的,若是View尚未測量完畢,那麼得到的寬/高就是0。因此在onCreate、onStart、onResume中均沒法正確獲得某個View的寬高信息。解決方式以下:
Activity/View#onWindowFocusChanged
// 此時View已經初始化完畢
// 當Activity的窗口獲得焦點和失去焦點時均會被調用一次
// 若是頻繁地進行onResume和onPause,那麼onWindowFocusChanged也會被頻繁地調用
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasureWidth(); int height = view.getMeasuredHeight(); }
}
// 經過post能夠將一個runnable投遞到消息隊列的尾部,// 而後等待Looper調用次runnable的時候,View也已經初 // 始化好了 protected void onStart() { super.onStart(); view.post(new Runnable() { @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
// 當View樹的狀態發生改變或者View樹內部的View的可見// 性發生改變時,onGlobalLayout方法將被回調 protected void onStart() { super.onStart(); ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @Override public void onGlobalLayout() { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
// ViewRootImpl.java private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ... } // View.java public void layout(int l, int t, int r, int b) { ... // 經過setFrame方法來設定View的四個頂點的位置,即View在父容器中的位置 boolean changed = isLayoutModeOptical(mParent) ? set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b); ... onLayout(changed, l, t, r, b); ... } // 空方法,子類若是是ViewGroup類型,則重寫這個方法,實現ViewGroup // 中全部View控件佈局流程 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
protected void onlayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l,) } } // layoutVertical核心源碼 void layoutVertical(int left, int top, int right, int bottom) { ... final int count = getVirtualChildCount(); for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasureWidth(); final int childHeight = child.getMeasuredHeight(); final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); ... if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } childTop += lp.topMargin; // 爲子元素肯定對應的位置 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); // childTop會逐漸增大,意味着後面的子元素會被 // 放置在靠下的位置 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child,i) } } } private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }
注意:在View的默認實現中,View的測量寬/高和最終寬/高是相等的,只不過測量寬/高造成於View的measure過程,而最終寬/高造成於View的layout過程,即二者的賦值時機不一樣,測量寬/高的賦值時機稍微早一些。在一些特殊的狀況下則二者不相等:
public void layout(int l, int t, int r, int b) { super.layout(l, t, r + 100, b + 100); }
private void performDraw() { ... draw(fullRefrawNeeded); ... } private void draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset, scalingRequired, dirty)) { return; } ... } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scallingRequired, Rect dirty) { ... mView.draw(canvas); ... } // 繪製基本上能夠分爲六個步驟 public void draw(Canvas canvas) { ... // 步驟一:繪製View的背景 drawBackground(canvas); ... // 步驟二:若是須要的話,保持canvas的圖層,爲fading作準備 saveCount = canvas.getSaveCount(); ... canvas.saveLayer(left, top, right, top + length, null, flags); ... // 步驟三:繪製View的內容 onDraw(canvas); ... // 步驟四:繪製View的子View dispatchDraw(canvas); ... // 步驟五:若是須要的話,繪製View的fading邊緣並恢復圖層 canvas.drawRect(left, top, right, top + length, p); ... canvas.restoreToCount(saveCount); ... // 步驟六:繪製View的裝飾(例如滾動條等等) onDrawForeground(canvas) }
// 若是一個View不須要繪製任何內容,那麼設置這個標記位爲true之後, // 系統會進行相應的優化。 public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
View的繪製流程和事件分發機制都是Android開發中的核心知識點,也是自定義View高手的內功心法。對於一名優秀的Android開發來講,主流三方源碼分析和Android核心源碼分析能夠說是必修課,下一篇,將會帶領你們更進一步深刻Android。
一、Android開發藝術探索
二、Android進階之光
三、Android高級進階
若是這個庫對您有很大幫助,您願意支持這個項目的進一步開發和這個項目的持續維護。你能夠掃描下面的二維碼,讓我喝一杯咖啡或啤酒。很是感謝您的捐贈。謝謝!
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc32595031fa?w=1080&h=1457&f=jpeg&s=93345" width=20%><img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc3259518ecd?w=990&h=1540&f=jpeg&s=110691" width=20%>
</div>
歡迎關注個人微信:
bcce5360
微信羣若是不能掃碼加入,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc352011e1fe?w=1013&h=1920&f=jpeg&s=86819" width=35%>
</div>
2千人QQ羣, Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~