框架分析
在之前的下拉刷新中,小結過觸屏消息先到WindowManagerService(Wms)然後順次傳遞給ViewRoot(派生自Handler),經decor view到Activity再傳遞給指定的View,這次整理View的繪製流程,通過源碼可知,這個過程應該沒有涉及到IPC(或者我沒有發現),需要繪製時在UI線程中通過ViewRoot發送一個異步請求消息,然後ViewRoot自己接收並不處理這個消息。
在正式進入View繪製之前,首先需要明確一下Android UI的架構組成,偷圖如下:
上述架構很清晰的呈現了Activity、Window、DecorView(及其組成)、ViewRoot和WMS之間的關係,我通過源碼簡單理了下從啓動Activity到創建View的過程,大致如下
在上圖中,performLaunchActivity函數是關鍵函數,除了新建被調用的Activity實例外,還負責確保Activity所在的應用程序啓動、讀取manifest中關於此activity設置的主題信息以及上圖中對“6.onCreate”調用也是通過對mInstrumentation.callActivityOnCreate來實現的。圖中的“8. mContentParent.addView”其實就是架構圖中phoneWindow內DecorView裏面的ContentViews,該對象是一個ViewGroup類實例。在調用AddView之後,最終就會觸發ViewRoot中的scheduleTraversals這個異步函數,從而進入ViewRoot的performTraversals函數,在performTraversals函數中就啓動了View的繪製流程。
performTraversals函數在2.3.5版本源碼中就有近六百行的代碼,跟我們繪製view相關的可以抽象成如下的簡單流程圖
流程圖中的host其實就是mView,而ViewRoot中的這個mView其實就是DecorView,之所以這麼說,又得具體看源碼中ActivityThread的handleResumeActivity函數,在這裏我就不展開了。上述流程主要調用了View的measure、layout和draw三個函數。
performTraversals(),該函數就是android系統View樹遍歷工作的核心。一眼看去,發現這個函數挺長的,但是邏輯是非常清晰的,其執行過程可簡單概括爲根據之前所有設置好的狀態,判斷是否需要計算視圖大小(measure)、是否需要重新安置視圖的位置(layout),以及是否需要重繪(draw)視圖,可以用以下圖來表示該流程。
- private void performTraversals() {
- // cache mView since it is used so much below...
- //1 處理mAttachInfo的初始化,並根據resize、visibility改變的情況,給相應的變量賦值。
- final View host = mView;
- final View.AttachInfo attachInfo = mAttachInfo;
- final int viewVisibility = getHostVisibility();
- boolean viewVisibilityChanged = mViewVisibility != viewVisibility
- || mNewSurfaceNeeded;
- float appScale = mAttachInfo.mApplicationScale;
- WindowManager.LayoutParams params = null;
- if (mWindowAttributesChanged) {
- mWindowAttributesChanged = false;
- surfaceChanged = true;
- params = lp;
- }
- Rect frame = mWinFrame;
- if (mFirst) {
- // For the very first time, tell the view hierarchy that it
- // is attached to the window. Note that at this point the surface
- // object is not initialized to its backing store, but soon it
- // will be (assuming the window is visible).
- attachInfo.mSurface = mSurface;
- attachInfo.mUse32BitDrawingCache = PixelFormat.formatHasAlpha(lp.format) ||
- lp.format == PixelFormat.RGBX_8888;
- attachInfo.mHasWindowFocus = false;
- attachInfo.mWindowVisibility = viewVisibility;
- ......
- }
- //2 如果mLayoutRequested判斷爲true,那麼說明需要重新layout,不過在此之前那必須重新measure。
- if (mLayoutRequested) {
- // Execute enqueued actions on every layout in case a view that was detached
- // enqueued an action after being detached
- getRunQueue().executeActions(attachInfo.mHandler);
- if (mFirst) {
- ......
- }
- }
- //3 判斷是否有子視圖的屬性發生變化,ViewRoot需要獲取這些變化。
- if (attachInfo.mRecomputeGlobalAttributes) {
- ......
- }
- if (mFirst || attachInfo.mViewVisibilityChanged) {
- ......
- }
- //4 根據上面得到的變量數值,確定我們的view需要多大尺寸才能裝下。於是就得measure了,有viewgroup的weight屬性還得再做些處理
- // Ask host how big it wants to be
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- mLayoutRequested = true;
- }
- }
- //5 measure完畢,接下來可以layout了。
- final boolean didLayout = mLayoutRequested;
- boolean triggerGlobalLayoutListener = didLayout
- || attachInfo.mRecomputeGlobalAttributes;
- if (didLayout) {
- host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
- }
- //6 如果mFirst爲true,那麼會進行view獲取焦點的動作。
- if (mFirst) {
- mRealFocusedView = mView.findFocus();
- }
- boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
- //7 終於,來到最後一步,前面的工作可以說都是鋪墊,都是爲了draw而準備的。
- if (!cancelDraw && !newSurface) {
- mFullRedrawNeeded = false;
- draw(fullRedrawNeeded);
- }
measure過程分析
因爲DecorView實際上是派生自FrameLayout的類,也即一個ViewGroup實例,該ViewGroup內部的ContentViews又是一個ViewGroup實例,依次內嵌View或ViewGroup形成一個View樹。所以measure函數的作用是爲整個View樹計算實際的大小,設置每個View對象的佈局大小(“窗口”大小)。實際對應屬性就是View中的mMeasuredHeight(高)和mMeasureWidth(寬)。
在View類中measure過程主要涉及三個函數,函數原型分別爲
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
前面兩個函數都是final類型的,不能重載,爲此在ViewGroup派生的非抽象類中我們必須重載onMeasure函數,實現measure的原理是:假如View還有子View,則measure子View,直到所有的子View完成measure操作之後,再measure自己。ViewGroup中提供的measureChild或measureChildWithMargins就是實現這個功能的。
在具體介紹測量原理之前還是先了解些基礎知識,即measure函數的參數由類measureSpec的makeMeasureSpec函數方法生成的一個32位整數,該整數的高兩位表示模式(Mode),低30位則是具體的尺寸大小(specSize)。
MeasureSpec有三種模式分別是UNSPECIFIED, EXACTLY和AT_MOST,各表示的意義如下
如果是AT_MOST,specSize代表的是最大可獲得的尺寸;
如果是EXACTLY,specSize代表的是精確的尺寸;
如果是UNSPECIFIED,對於控件尺寸來說,沒有任何參考意義。
那麼對於一個View的上述Mode和specSize值默認是怎麼獲取的呢,他們是根據View的LayoutParams參數來獲取的:
參數爲fill_parent/match_parent時,Mode爲EXACTLY,specSize爲剩餘的所有空間;
參數爲具體的數值,比如像素值(px或dp),Mode爲EXACTLY,specSize爲傳入的值;
參數爲wrap_content,Mode爲AT_MOST,specSize運行時決定。
具體測量原理
上面提供的Mode和specSize只是程序員對View的一個期望尺寸,最終一個View對象能從父視圖得到多大的允許尺寸則由子視圖期望尺寸和父視圖能力尺寸(可提供的尺寸)兩方面決定。關於期望尺寸的設定,可以通過在佈局資源文件中定義的android:layout_width和android:layout_height來設定,也可以通過代碼在addView函數調用時傳入的LayoutParams參數來設定。父View的能力尺寸歸根到最後就是DecorView尺寸,這個尺寸是全屏,由手機的分辨率決定。期望尺寸、能力尺寸和最終允許尺寸的關係,我們可以通過閱讀measureChild或measureChildWithMargins都會調用的getChildMeasureSpec函數的源碼來獲得,下面簡單列表說明下三者的關係
父視圖能力尺寸 |
子視圖期望尺寸 |
子視圖最終允許尺寸 |
EXACTLY + Size1 |
EXACTLY + Size2 |
EXACTLY + Size2 |
EXACTLY + Size1 |
fill_parent/match_parent |
EXACTLY+Size1 |
EXACTLY + Size1 |
wrap_content |
AT_MOST+Size1 |
AT_MOST+Size1 |
EXACTLY + Size2 |
EXACTLY+Size2 |
AT_MOST+Size1 |
fill_parent/match_parent |
AT_MOST+Size1 |
AT_MOST+Size1 |
wrap_content |
AT_MOST+Size1 |
UNSPECIFIED+Size1 |
EXACTLY + Size2 |
EXACTLY + Size2 |
UNSPECIFIED+Size1 |
fill_parent/match_parent |
UNSPECIFIED+0 |
UNSPECIFIED+Size1 |
wrap_content |
UNSPECIFIED+0 |
上述表格展現的是子視圖最終允許得到的尺寸,顯然1、4、7三項沒有對Size1和Size2進行比較,所以允許尺寸是可以大於父視圖的能力尺寸的,這個時候最終的視圖尺寸該是多少呢?AT_MOST和UNSPECIFIED的View又該如何決策最終的尺寸呢?
通過Demo演示的得到的結果,假如Size2比Size1的尺寸大,假如不使用滾動效果的話,子視圖超出部分將被裁剪掉,該父視圖中如果在該子視圖後面還有其他視圖,那麼也將被裁剪掉,但是通過調用其getVisibility還是顯示該控件是可見的,所以裁剪後控件依然是有的,只是用戶沒辦法觀察到;在使用滾動效果的情況下,就能將原本被裁剪掉的控件通過滾動顯示出來。
對於第二個問題,根據源碼View的OnMeasure函數調用的getDefaultSize函數獲知,默認情況下,控件都有一個最小尺寸,該值可以通過設置android:minHeight和android:minWidth來設置(無設置時缺省爲0);在設置了背景的情況下,背景drawable的最小尺寸與前面設置的最小尺寸比較,兩者取大者,作爲控件的最小尺寸。在UNSPECIFIED情況下就選用這個最小尺寸,其它情況則根據允許尺寸來。不過這個是默認規則,通過demo發現,TextView在AT_MOST+Size情況下,並不是以Size作爲控件的最終尺寸,結果發現在TextView的源碼中,重載了onMeasure函數,有價值的代碼如下:
……
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
……
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
……
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desired, heightSize);
}
……
至於其中的width和desired值,感興趣的同學可以具體關注下。雖然FrameWork提供了視圖默認的尺寸計算規則,但是最終的視圖佈局大小可以重載onMeasure函數來修改計算規則,當然也可以不計算直接通過setMeasuredDimension來設置(需要注意的是,如果通過setMeasuredDimension的同時還要調用父類的onMeasure函數,那麼在調用父類函數之前調用的setMeasuredDimension會無效果)。
計算視圖大小(measure)的過程
整個view視圖的Measure過程就是一個量體裁衣,按需分配的過程。看一下以下的遞歸過程:
從上圖可以看出,measure過程始於ViewRoot的host.measure(),調的就是view類的measure()函數,該函數然後回調onMeasure。如果host對象是一個ViewGroup實例,一般會重載onMeasure,如果沒有的話,則會執行view類中默認的onMeasure。合理的情況是編程人員重載onMeasure並逐一對裏面的子view進行measure。我們可以看一下view的measure方法:
- /**
- * 該方法在需要確定view所需尺寸大小時調用,父視圖會提供寬和高的屬性約束。
- * 具體視圖完全可以在onMeasure中改變這些。
- * @see #onMeasure(int, int)
- */
- public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
- if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
- widthMeasureSpec != mOldWidthMeasureSpec ||
- heightMeasureSpec != mOldHeightMeasureSpec) {
- // 首先清除dimension的設值
- mPrivateFlags &= ~MEASURED_DIMENSION_SET;
- // measure 自己, 並設置dimension
- onMeasure(widthMeasureSpec, heightMeasureSpec);
- // 拋出未設值flag的異常
- if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
- throw new IllegalStateException("onMeasure() did not set the"
- + " measured dimension by calling"
- + " setMeasuredDimension()");
- }
- mPrivateFlags |= LAYOUT_REQUIRED;
- }
- mOldWidthMeasureSpec = widthMeasureSpec;
- mOldHeightMeasureSpec = heightMeasureSpec;
- }
這裏強烈建議去看看viewGroup實例FrameLayout和LinearLayout的onMeasure方法,一定會有所感悟的,尤其是LinerLayout的。這樣對於viewGroup的專用標籤pading和weight也會有新的體會。
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="100dip"
- >
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="20dip"
- android:layout_weight="2"
- android:text="@string/hello"
- />
- <ListView
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#ff00ff00"
- ></ListView>
- </LinearLayout>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="100dip"
- >
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="60dip"
- android:layout_weight="2"
- android:text="@string/hello"
- />
- <ListView
- android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="2"
- android:background="#ff00ff00"
- ></ListView>
- </LinearLayout>
請問以上兩佈局有無不同,能否自行畫出?
佈局(layout)過程
執行完measure過程,也就是說各個view的大小尺寸已經登記在案,現在它們要確定的是自己應該置身於何處,也就是擺放在哪裏。好吧,這個就是layout的職責所在,讓父視圖按照子視圖的大小及佈局參數,將子視圖放置在合適的位置上。
同樣需要看下以下流程圖:
- public void layout(int l, int t, int r, int b) {
- int oldL = mLeft;
- int oldT = mTop;
- int oldB = mBottom;
- int oldR = mRight;
- //調用setFrame()函數給當前視圖設置參數中指定的位置
- boolean changed = setFrame(l, t, r, b);
- if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
- }
- //回調onLayout()函數
- onLayout(changed, l, t, r, b);
- mPrivateFlags &= ~LAYOUT_REQUIRED;
- //4.0新增監聽可捕獲layout變化
- if (mOnLayoutChangeListeners != null) {
- ArrayList<OnLayoutChangeListener> listenersCopy =
- (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
- int numListeners = listenersCopy.size();
- for (int i = 0; i < numListeners; ++i) {
- listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
- }
- }
- }
- //layout完成,清楚標籤
- mPrivateFlags &= ~FORCE_LAYOUT;
- }
view中的該layout函數流程大概如下:
1,調用setFrame()將位置參數保存,這些參數會保存到view內部變量(mLeft,mTop,mRight,mButtom)。如果有數值的變化那麼會調用invalidate()重繪那些區域。
2,回調onLayout(),View中定義的onLayout()函數默認什麼都不做,View系統提供onLayout函數的目的是爲了使系統包含有子視圖的父視圖能夠在onLayout()函數對子視圖進行位置分配,正因爲這樣,如果是viewGroup類型,就必須重載onLayout(),由此ViewGroup的onLayout爲abstract也就很好解釋了。
3,清楚mPrivateFlags中的LAYOUT_REQUIRED標記,因爲layout的操作已經完成了。
爲了對layout過程有更深的體會,有必要針對特定的一種viewGroup進行分析,我們還是把魔爪伸向linearLayout,看看它的onMeasure,onMeasure中會根據mOrientation變量,進行不同的layout,我們看一種就行了:
- void layoutVertical() {
- final int paddingLeft = mPaddingLeft;
- int childTop;
- int childLeft;
- // 獲得子視圖可以用的寬度,順便也把子視圖左邊沿的位置計算出來。
- final int width = mRight - mLeft;
- int childRight = width - mPaddingRight;
- // Space available for child
- int childSpace = width - paddingLeft - mPaddingRight;
- final int count = getVirtualChildCount();
- //根據父視圖中的gravity屬性,決定子視圖的起始位置。
- final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
- final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
- switch (majorGravity) {
- case Gravity.BOTTOM:
- // mTotalLength contains the padding already
- childTop = mPaddingTop + mBottom - mTop - mTotalLength;
- break;
- // mTotalLength contains the padding already
- case Gravity.CENTER_VERTICAL:
- childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;
- break;
- case Gravity.TOP:
- default:
- childTop = mPaddingTop;
- break;
- }
- //遍歷所有子視圖,爲它們分配位置.setChildFrame()結果還是會調用child.layout()爲子視圖設置佈局位置.
- for (int i = 0; i < count; i++) {
- final View child = getVirtualChildAt(i);
- if (child == null) {
- childTop += measureNullChild(i);
- ......
- childTop += lp.topMargin;
- setChildFrame(child, childLeft, childTop + getLocationOffset(child),
- childWidth, childHeight);
- childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
- i += getChildrenSkipCount(child, i);
- }
- }
- }
繪製(draw)過程
這是見證奇蹟的時刻,draw過程就是要把view對象繪製到屏幕上,如果它是一個viewGroup,則需要遞歸繪製它所有的子視圖。視圖中有哪些元素是需要繪製的呢?
1,view背景。所有view都會有一個背景,可以是一個顏色值,也可以是一張背景圖片
2,視圖本身內容。比如TextView的文字
3,漸變邊框。就是一個shader對象,讓視圖看起來更具有層次感
4,滾動條。
按照慣例,還是先看一下繪製的總體流程吧:
眼尖的同學應該發現了,上面這張圖比前面的measure和layout多了一步:draw()。在performTraversals()函數中調用的是viewRoot的draw()函數,在該函數中進行一系列的前端處理後,再調用host.draw()。
一般情況下,View對象不應該重載draw()函數,因此,host.draw()就是view.draw()。該函數內部過程也就是View系統繪製過程的核心過程,該函數中會依次繪製前面所說哦四種元素,其中繪製視圖本身的具體實現就是回調onDraw()函數,應用程序一般也會重載onDraw()函數以繪製所設計的View的真正界面內容。
Google源碼的註釋太nice了,我加任何說辭都顯得多餘,就不畫蛇添足了:
- public void draw(Canvas canvas) {
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
- }
- final int privateFlags = mPrivateFlags;
- final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
- (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
- mPrivateFlags = (privateFlags & ~DIRTY_MASK) | 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) {
- final Drawable background = mBGDrawable;
- 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);
- // we're done...
- return;
- }
- /*
- * Here we do the full fledged routine...
- * (this is an uncommon case where speed matters less,
- * this is why we repeat some of the tests that have been
- * done above)
- */
- boolean drawTop = false;
- boolean drawBottom = false;
- boolean drawLeft = false;
- boolean drawRight = false;
- float topFadeStrength = 0.0f;
- float bottomFadeStrength = 0.0f;
- float leftFadeStrength = 0.0f;
- float rightFadeStrength = 0.0f;
- // Step 2, save the canvas' layers
- int paddingLeft = mPaddingLeft;
- final boolean offsetRequired = isPaddingOffsetRequired();
- if (offsetRequired) {
- paddingLeft += getLeftPaddingOffset();
- }
- int left = mScrollX + paddingLeft;
- int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
- int top = mScrollY + getFadeTop(offsetRequired);
- int bottom = top + getFadeHeight(offsetRequired);
- if (offsetRequired) {
- right += getRightPaddingOffset();
- bottom += getBottomPaddingOffset();
- }
- final ScrollabilityCache scrollabilityCache = mScrollCache;
- final float fadeHeight = scrollabilityCache.fadingEdgeLength;
- int length = (int) fadeHeight;
- // clip the fade length if top and bottom fades overlap
- // overlapping fades produce odd-looking artifacts
- if (verticalEdges && (top + length > bottom - length)) {
- length = (bottom - top) / 2;
- }
- // also clip horizontal fades if necessary
- if (horizontalEdges && (left + length > right - length)) {
- length = (right - left) / 2;
- }
- if (verticalEdges) {
- topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
- drawTop = topFadeStrength * fadeHeight > 1.0f;
- bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
- drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
- }
- saveCount = canvas.getSaveCount();
- int solidColor = getSolidColor();
- if (solidColor == 0) {
- final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
- if (drawTop) {
- canvas.saveLayer(left, top, right, top + length, null, flags);
- }
- if (drawBottom) {
- canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
- }
- } else {
- scrollabilityCache.setFadeColor(solidColor);
- }
- // 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
- final Paint p = scrollabilityCache.paint;
- final Matrix matrix = scrollabilityCache.matrix;
- final Shader fade = scrollabilityCache.shader;
- if (drawTop) {
- matrix.setScale(1, fadeHeight * topFadeStrength);
- matrix.postTranslate(left, top);
- fade.setLocalMatrix(matrix);
- canvas.drawRect(left, top, right, top + length, p);
- }
- 。。。。。
- canvas.restoreToCount(saveCount);
- // Step 6, draw decorations (scrollbars)
- onDrawScrollBars(canvas);
- }
繪製完界面內容後,如果該視圖還包含子視圖,則調用dispatchDraw()函數,實際上起作用的還是viewGroup的dispatchDraw()函數。需要說明的是應用程序不應該再重載ViewGroup中該方法,因爲它已經有了默認而且標準的view系統流程。dispatchDraw()內部for循環調用drawChild()分別繪製每一個子視圖,而drawChild()內部又會調用draw()函數完成子視圖的內部繪製工作。
- protected void dispatchDraw(Canvas canvas) {
- final int count = mChildrenCount;
- final View[] children = mChildren;
- int flags = mGroupFlags;
- //1 判斷mGroupFlags是否設有FLAG_RUN_ANIMATION標識並且不爲0.該layout動畫指的是加載或移除子視圖時候呈現的動畫.
- if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
- final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
- final boolean buildCache = !isHardwareAccelerated();//硬件加速,4.0加入.
- for (int i = 0; i < count; i++) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
- final LayoutParams params = child.getLayoutParams();
- attachLayoutAnimationParameters(child, params, i, count);
- bindLayoutAnimation(child);
- }
- }
- //2 處理padding屬性,如果該viewGroup有設置.
- 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);
- }
- //3 開始繪製子視圖動畫之前先清除flag.
- // We will draw our child's animation, let's reset the flag
- mPrivateFlags &= ~DRAW_ANIMATION;
- mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
- boolean more = false;
- final long drawingTime = getDrawingTime();
- //4 使用佛如循環,使viewGroup的子視圖逐個調用drawChild函數.
- 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);
- }
- }
- }
- //5 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);
- }
- }
- if (clipToPadding) {
- canvas.restoreToCount(saveCount);
- }
- // mGroupFlags might have been updated by drawChild()
- flags = mGroupFlags;
- if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
- invalidate(true);
- }
- }