View的繪製流程原始碼解析
提起View的繪製流程,相信大家立刻就能想到耳熟聞詳的三個方法:onMeasure(測量)、onLayout(佈局)、onDraw(繪製),這三個方法的確參與了View的繪製流程,除此之外還有MeasureSpec、LayoutParams等等。今天筆者就帶領大家由頂級View:DecorView開始,從上至下,將View的繪製流程整體貫穿一下,希望大家閱讀完本文後都能有所收穫。
瞭解Activity視窗機制的朋友們肯定知道,DecorView作為Activity的頂級View,它本身是一個FrameLayout,DecorView下只有一個直接的子View,那就是LinearLayout,該LinearLayout的排列方式為豎直排列,包括有標題欄和id為content的內容部分,我們在Activity的onCreate方法中通過setContentView載入的佈局檔案就是作為直接子View add到這個id為content的FrameLayout中。好了,在這裡我新建立一個Activity,對應的佈局檔案如下,下面我們就以此為例,由DecorView開始,從上至下來分析下View的繪製流程。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="View 的繪製流程解析"/> </LinearLayout>
View繪製流程的起點為ViewRootImpl類中的performTraversals()方法,我們開啟原始碼看下(為了便於分析,原始碼有所刪減):
private void performTraversals() { ... //1.獲取DecorView的測量規則,其中mWidth為螢幕的寬度,mHeight為螢幕的高度 //lp為DecorView自身的佈局引數LayoutParams int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); //2.完成整個ViewTree的測量工作 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... //3.完成整個ViewTree的佈局工作 performLayout(lp, mWidth, mHeight); ... //4.完成整個ViewTree的繪製工作 performDraw(); }
可以看到,在performTraversals方法中,首先呼叫到getRootMeasureSpec方法,建立頂級View DecorView的測量規則,接著依次呼叫到performMeasure、performLayout、performDraw方法,分別完成整個ViewTree的測量、佈局、以及繪製工作。
我們首先看下1處DecorView MeasureSpec的建立,在這裡我先簡單說一下,DecorView測量規則的建立和我們普通View(包括有View以及ViewGroup)測量規則的建立略有不同。對於DecorView而言,它的MeasureSpec是由視窗尺寸和它自身的LayoutParams共同決定的,而我們的普通View,它的MeasureSpec則是由父容器的MeasureSpec和它自身的LayoutParams共同決定的。這一點通過原始碼就可以看出來。1處呼叫到getRootMeasureSpec方法,完成DecorView MeasureSpec的建立,getRootMeasureSpec方法中傳的第一個引數為螢幕的寬高,第二個引數為DecorView自身的LayoutParams,不知道你有沒有好奇DecorView的佈局引數具體是什麼呢?我們點進去 lp ,看下它是在哪裡賦值的:
private void performTraversals() { ... WindowManager.LayoutParams lp = mWindowAttributes; }
你沒看錯,就是在performTraversals方法中一開始賦值的,那 mWindowAttributes 又是在哪裡賦值的呢,我們接著跟:
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
mWindowAttributes 為ViewRootImpl類中定義的final型別的成員變數,我們跟進去WindowManager.LayoutParams:
# WindowManager public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { ... public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = TYPE_APPLICATION; format = PixelFormat.OPAQUE; } ... }
這下真相大白了吧,DecorView的佈局引數lp.width為MATCH_PARENT,lp.height為MATCH_PARENT。好了好了,我們接著回到performTraversals()中的1處,跟進去getRootMeasureSpec方法,看下DecorView的MeasureSpec具體是怎麼建立的:
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { //1. case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; //2. case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; //3. default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
通過上述程式碼,DecorView MeasureSpec的建立過程就很明確了,由於DecorView的佈局引數lp.width為MATCH_PARENT,lp.height為MATCH_PARENT,所以程式實際上只會走第一個case語句。即DecorView 寬度SpecMode為MeasureSpec.EXACTLY,SpecSize為螢幕寬度,高度SpecMode為MeasureSpec.EXACTLY,SpecSize為螢幕高度。
我們接著回到performTraversals方法中,接著就是2處的performMeasure方法,跟進去:
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); } }
performMeasure方法中傳入的第一個引數childWidthMeasureSpec就是decorview寬度測量規則,傳入的第二個引數childHeightMeasureSpec就是decorview高度測量規則,mView就是我們的decorview物件,可以看到performMeasure方法中呼叫到decorview的measure方法,將decorview的寬度測量規則和高度測量規則直接作為引數傳入。我們跟進去decorview的measure方法看下(DecorView本質為FrameLayout,FrameLayout繼承自ViewGroup,ViewGroup繼承自View類,實質呼叫到View類的measure方法):
#View public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... onMeasure(widthMeasureSpec, heightMeasureSpec); ... }
接著跟進去decorview的onMeasure方法:
#DecorView @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ... //重點,呼叫到FrameLayout的onMeasure方法 super.onMeasure(widthMeasureSpec, heightMeasureSpec); ... }
我們來到FrameLayout類的onMeasure方法看下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //獲取到子View的數量,在這裡由於我們的decorview只有一個直接的子View,那就是一個豎直排列的LinearLayout,所以count為1 int count = getChildCount(); //由我們上述分析可知,decorview的寬度測量規則為MeasureSpec.EXACTLY,高度測量規則為MeasureSpec.EXACTLY //所以在這裡measureMatchParentChildren的值 計算為false final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; //mMatchParentChildren為ArrayList,呼叫mMatchParentChildren.clear()方法後,mMatchParentChildren的size為0 mMatchParentChildren.clear(); // maxHeight 代表framelayout中最大高度 // maxWidth 代表framelayout中最大寬度 int maxHeight = 0; int maxWidth = 0; int childState = 0; //遍歷framelayout中的所有子View,完成以該framelayout物件為首的ViewTree,其下所有子View的測量工作 for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { //1. 重點!!!對子View進行測量 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //由FrameLayout的佈局特點決定,framelayout的寬度為所有子view的最大寬度(將子view的leftMargin 以及 rightMargin考慮在內)+ 該framelayout的leftpadding 和 rightpadding maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); //同上 maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); //在這裡mMatchParentChildren 為false,所以 mMatchParentChildren的 size 一直為 0 if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } //考慮到framelayout的padding maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // 考慮到framelayout的最小寬度和高度 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } //2. 設定framelayout的測量寬高,在這裡為設定decorview的測量寬高 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); //在這裡count為 0 ,if 語句程式碼塊不執行 if (count > 1) { ... } }
上述程式碼中重點部分已經做了詳細的標註,相信大家都能理解,在這裡FrameLayout就是我們的decorview物件,其下只有一個子View,那就是一個豎直排列的LinearLayout。所以measureChildWithMargins方法在這裡完成了decorview中LinearLayout物件的測量。我們跟進去measureChildWithMargins方法看下:
#ViewGroup類 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 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); }
可以看到,在 measureChildWithMargins 方法中,首先獲取到子View的LayoutParams,然後呼叫getChildMeasureSpec方法來建立子View的MeasureSpec,最後呼叫到子View的measure方法,將子View的MeasureSpec作為引數傳入。同時我們還可以分析出,子View MeasureSpec的建立與父容器的MeasureSpec和子View本身的LayoutParams有關,除此之外,還與父容器的padding以及子View的margin有關。我們跟進去 getChildMeasureSpec方法,看下子View的MeasureSpec具體是怎麼建立的:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //獲取到父容器的測量模式 int specMode = MeasureSpec.getMode(spec); //獲取到父容器的測量大小 int specSize = MeasureSpec.getSize(spec); //padding指父容器中已佔用空間大小,size為當前子View最大可用大小 int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // 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 (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 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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
上述程式碼清楚展示了普通View的MeasureSpec的建立規則,在這裡就不贅述了。
好了,我們回到measureChildWithMargins方法中接著往下看,在這裡decorview中子View LinearLayout物件的MeasureSpec建立完畢後,後續呼叫到LinearLayout的measure方法,將LinearLayout的測量規則傳入,我們跟進去:
#View同樣是呼叫到View類 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... onMeasure(widthMeasureSpec, heightMeasureSpec); ... }
由於LinearLayout重寫了View類的onMeasure方法,所以我們跟進去LinearLayout的onMeasure方法看一下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } }
由上述程式碼可以看到,在LinearLayout的onMeasure方法中首先對 mOrientation的值進行了判斷,mOrientation代表當前LinearLayout的排列順序,我們說過,decorview中的LinearLayout的排列方式為豎直排列,所以 mOrientation == VERTICAL 的值為 true,我們跟進去 measureVertical方法去看下:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { // See how tall everyone is. Also remember max width. for (int i = 0; i < count; ++i) { ... //1. measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight); ... } //2. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState); ... }
在LinearLayout的 measureVertical 方法中,同樣是首先遍歷當前LinearLayout下的所有子View,呼叫 measureChildBeforeLayout 方法完成當前LinearLayout下所有子View的測量工作,最後呼叫到 setMeasuredDimension方法,設定自己的測量寬高。我們跟進去 measureChildBeforeLayout方法:
#LinearLayout void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight) { measureChildWithMargins(child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight); }
哈哈哈,LinearLayout的 measureChildBeforeLayout方法中同樣是呼叫到ViewGroup類的 measureChildWithMargins方法,首先獲取到子View的MeasureSpec,然後呼叫到子View的measure方法來完成子View的測量工作。由於當前LinearLayout物件為DecorView的直接子View,所以當前LinearLayout的子View就是titlebar標題欄和id為content的FrameLayout。後續的測量過程類似,在這裡我就不再贅述了。。。
好了,我們回過頭接著分析下decorview中LinearLayout測量完畢後的操作,那就是2處的setMeasuredDimension方法。setMeasuredDimension方法接收兩個引數,第一個引數為measuredWidth,就是測量後的寬度,第二個引數為measuredHeight,就是測量後的高度。由上述程式碼可以看到,decorview寬高的測量值是由resolveSizeAndState方法決定的,我們跟進去看下:
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
由於decorview的寬度SpecMode和高度SpecMode都為MeasureSpec.EXACTLY,寬度的SpecSize和高度SpecSize分別為 螢幕寬度 和
螢幕高度 ,所以針對decorview而言,在resolveSizeAndState方法中只會走 case MeasureSpec.EXACTLY:程式碼塊,即將螢幕寬度和螢幕高度直接return掉,我們接著看下setMeasuredDimension方法:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth= insets.left + insets.right; int opticalHeight = insets.top+ insets.bottom; measuredWidth+= optical ? opticalWidth: -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } //重點 setMeasuredDimensionRaw(measuredWidth, measuredHeight); }
接著跟進去setMeasuredDimensionRaw方法:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
可以看到在setMeasuredDimensionRaw方法中直接將我們傳進來的measuredWidth 和 measuredHeight分別賦值給 decorview的成員變數: mMeasuredWidth 和 mMeasuredHeight 。即decorview的測量寬度為螢幕寬度,測量高度為螢幕高度。
按照程式程式碼的執行流程,到此為止,整個ViewTree的測量工作就完畢了,接下來程式會回到 ViewRootImpl 類中的 performTraversals方法接著執行 3 處的performLayout方法,來完成整個ViewTree的佈局工作。我們跟進去看下:
#ViewRootImpl private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; //首先判斷decorview物件是否為null,如果decorview為null,直接return掉 final View host = mView; if (host == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { //重點!!!呼叫到decorview的layout方法 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mInLayout = false; ... } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }
我們接著跟進去decorview的layout方法看下:
#ViewGroup類 //對View類的layout方法進行了重寫,所以首先會呼叫到ViewGroup類中的layout方法 @Override public final void layout(int l, int t, int r, int b) { if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) { if (mTransition != null) { mTransition.layoutChange(this); } //呼叫到View類中的layout方法 super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes mLayoutCalledWhileSuppressed = true; } }
我們接著跟進去View類的layout方法:
#View public void layout(int l, int t, int r, int b) { ... //1.呼叫setFrame方法,確定View本身的位置,在這裡是確定decorview自身的位置 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //2.呼叫到onLayout方法,確定子View的位置,在這裡是確定decorview下LinearLayout的位置 onLayout(changed, l, t, r, b); ... } }
我們首先跟進去1處的setFrame方法:
protected boolean setFrame(int left, int top, int right, int bottom) { ... mLeft = left; mTop = top; mRight = right; mBottom = bottom; ... }
可以看到在setFrame方法中將我們傳進來的left、top、right、bottom分別賦值給mLeft、mTop、mRight、mBottom。就這樣,decorview的位置就確定了。我們回過頭接著看 2處decorview的onLayout方法,跟進去看下:
#DecorView @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { //呼叫到FrameLayout類中的onLayout方法 super.onLayout(changed, left, top, right, bottom); ... }
我們跟進去FrameLayout類中看下:
#FrameLayout @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); }
接著跟進去layoutChildren方法:
#FrameLayout void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { //獲取到framelayout中子View的數量 final int count = getChildCount(); //分別獲取到子View的left、right、top、bottom的邊界座標區域, //座標系轉換,以當前父容器的左上角為座標系原點,水平向右為X軸正向,豎直向下為Y軸正向。 //在這裡 layoutChildren 方法傳入的 left、top、right、bottom的值是當前父容器作為子View相對於其父容器來講的座標值,經過相減操作後就將座標系轉換成以當前父容器為基準座標系 final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //獲取到當前子View的測量寬高值 final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; //確定當前子View的left座標 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } //確定當前子View的top座標 switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } //重點!!!呼叫到子View的layout方法,將當前子View的left、top、right、bottom座標值作為引數傳入 child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
上述程式碼中的註釋已經很清楚了,在這裡,當前父容器指的就是我們的decorview,所以計算好decorview下 linearLayout的座標值後會呼叫LinearLayout的layout方法,確定linearLayout的位置,我們跟進去LinearLayout的layout方法去看下(同樣呼叫到View類的layout方法):
#View public void layout(int l, int t, int r, int b) { ... //1.呼叫setFrame方法,確定View本身的位置,在這裡是確定decorview下LinearLayout自身的位置 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //2.呼叫到onLayout方法,確定子View的位置,在這裡是確定LinearLayout下titlebar和id為content的FrameLayout的位置 onLayout(changed, l, t, r, b); ... } }
LinearLayout類對onLayout方法進行了重寫,所以我們跟進去看下:
#LinearLayout @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } }
decorview下的LinearLayout的排列方式為豎直排列,mOrientation == VERTICAL 的值為true,所以我們來到 layoutVertical方法:
#LinearLayout void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; // 獲取到子View的right邊界座標值 final int width = right - left; int childRight = width - mPaddingRight; // 子View水平方向可用空間 int childSpace = width - paddingLeft - mPaddingRight; //獲取到子view的數量 final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; //根據當前linearlayout的gravity屬性值確定第一個子View的top座標值 switch (majorGravity) { case Gravity.BOTTOM: // mTotalLength contains the padding already childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already case Gravity.CENTER_VERTICAL: childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; case Gravity.TOP: default: childTop = mPaddingTop; break; } for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { //獲取到LinearLayout下子View的測量寬高 final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); //根據子View的屬性值layout_gravity,確定子View的left座標值 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; } if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } //考慮到子View的layout_marginTop值 childTop += lp.topMargin; //1. 呼叫到setChildFrame方法,確定子View的座標位置 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); //當前父容器linearlayout的排列方式為豎直排列,所以childTop的值需要自增加上當前子View的測量高度以及子View bottomMargin的值 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } }
我們接著跟進去1處的 setChildFrame方法:
#LinearLayout private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }
可以看到在LinearLayout的 setChildFrame 方法中直接呼叫到子View的layout方法,將子View的left、top、right、bottom座標值作為引數傳了進去。由於我們當前的LinearLayout為DecorView下的LinearLayout,所以這裡的子View為titleBar和id為content的FrameLayout。後續的layout流程類似,這裡筆者就贅述了。。。
整個ViewTree的layout佈局工作完成以後,程式會回到 ViewRootImpl 類中的performTraversals()方法接著向下執行,也就是呼叫到 4 處的 performDraw 方法,完成整個ViewTree的繪製工作,我們跟進去看下:
#ViewRootImpl private void performDraw() { ... try { //重點 draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ... }
我們接著跟進去 ViewRootImpl 類的draw方法去看下:
private void draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } ... }
我們緊著跟進去 drawSoftware 方法:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { ... try { //調整畫布位置 canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; //重點!!!呼叫到decorview的draw方法 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; } } ... }
在 ViewRootImpl 類中的 drawSoftware 方法中終於呼叫到 decorview的draw方法進行繪製操作,我們跟進去看下:
#DecorView @Override public void draw(Canvas canvas) { //呼叫到View類中的draw方法 super.draw(canvas); if (mMenuBackground != null) { mMenuBackground.draw(canvas); } }
可以看到在DecorView的draw方法中直接呼叫到 View類的draw方法,我們跟進去看下:
#View類 @CallSuper public void draw(Canvas canvas) { 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; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * *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) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // 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); drawAutofilledHighlight(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // Step 7, draw the default focus highlight drawDefaultFocusHighlight(canvas); if (debugDraw()) { debugDrawFocus(canvas); } // we're done... return; } ... }
從上述程式碼可以看出,View的繪製過程遵循如下幾步:1. 呼叫 drawBackground 方法繪製自身的背景2. 呼叫 onDraw 方法繪製自己3. 呼叫 dispatchDraw 方法繪製children4. 呼叫 onDrawForeground 方法繪製裝飾。
在這裡當前View為decorview,所以decorview首先呼叫到onDraw方法繪製自己,然後呼叫到 dispatchDraw 方法繪製children,也就是decorview下的linearlayout物件。我們首先看下decorview的onDraw方法:
#DecorView @Override public void onDraw(Canvas c) { //重點,直接呼叫到View類的onDraw方法 super.onDraw(c); // When we are resizing, we need the fallback background to cover the area where we have our // system bar background views as the navigation bar will be hidden during resizing. mBackgroundFallback.draw(isResizing() ? this : mContentRoot, mContentRoot, c, mWindow.mContentParent); }
我們跟進去View類的onDraw方法去看下:
/** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { }
對的你沒有看錯,decorview的onDraw方法內部只是呼叫到 mBackgroundFallback.draw,具體什麼操作我們就不追究了,我們回過頭接著看下decorview的dispatchDraw方法:
#ViewGroup @Override protected void dispatchDraw(Canvas canvas) { ... more |= drawChild(canvas, child, drawingTime); ... }
接著看下 drawChild 方法:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { //重點 return child.draw(canvas, this, drawingTime); }
可以看到在 drawChild 方法中直接呼叫到 child.draw方法,由於我們當前View為decorview,也就是呼叫到decorview下linearlayout的draw方法,後續繪製流程類似,這裡就不再贅述了。。。
整個ViewTree的繪製工作執行完畢後,確切說,ViewRootImpl 類中的performTraversals()方法就執行完畢了。
到這裡為止,我們就從頂級View DecorView從上至下,將整個ViewTree的繪製流程貫穿了一遍。文章略長,感謝大家有耐心讀完本文。