Android View 原始碼解析(三) - View的繪製過程
現在開始分析View的繪製機制
View的測量 佈局 繪製過程
測量之前的事情
View的整個繪製流程是開始於ViewRootImpl類的performTraversals方法(1k行) 根據相關設定來覺得十分要重新執行相關功能
private void performTraversals() { // cache mView since it is used so much below... final View host = mView; ... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ... //measure mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ... //layout mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ... //draw mView.draw(canvas); ... }
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; ... } return measureSpec; }
View 繪製流程圖如下

measure原始碼分析
結論:
-
measure的過程就是父View向子View遞迴呼叫view.measure方法 (measure中回撥onMeasure方法)的過程
-
measure方法是 final的 只能過載onMeasure方法
-
最頂層的DocerView的MeasureSpec由ViewRootImpl的getRootMeasureSpec方法提供 LayoutParams的引數為MATCH_PARENT specMode是EXACTLY,specSize為物理螢幕大小
-
只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams 否則無法使用layout_margin引數
-
View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,要必須保證這兩個方法在onMeasure流程之後被呼叫才能返回有效值。

View measure過程
/** * <p> * This is called to find out how big a view should be. The parent supplies constraint information in the width and height parameters. * </p> * * <p> * The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only * {@link #onMeasure(int, int)} can and must be overridden by subclasses. * </p> * * * @param widthMeasureSpec Horizontal space requirements as imposed by the *parent * @param heightMeasureSpec Vertical space requirements as imposed by the *parent * * @see #onMeasure(int, int) */ //沒捨得刪這些註釋感覺重要的事情都說了為了計算整個View樹的實際大小 設定實際的高和寬 每個子View都是根據父檢視和自身決定實際寬高的 在onMeasure()方法中進行實際測量.傳入widthMeasureSpec和heightMeasureSpec引數來表示了父View的規格 不但傳入了模式 還傳入了size 而對於DecorView來說 傳入的模式一般為EXACTLY模式 size對應螢幕的寬高. 所以說子View的大小是父子View共同決定的 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); }
MeasureSpec內部類
MeasureSpec是View的內部類 int型,由高2位規格模式specMode和低30位具體尺寸specSize組成 其中specMode只有三種
- MeasureSpec.EXACTLY //確定模式,父View希望子View的大小是確定的,由specSize決定;
- MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
- MeasureSpec.UNSPECIFIED //未指定模式,父View完全依據子View的設計值來決定;
onMeasure()方法
/** * <p> * Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and * should be overridden by subclasses to provide accurate and efficient * measurement of their contents. * </p> * * <p> * <strong>CONTRACT:</strong> When overriding this method, you * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the * measured width and height of this view. Failure to do so will trigger an * <code>IllegalStateException</code>, thrown by * {@link #measure(int, int)}. Calling the superclass' * {@link #onMeasure(int, int)} is a valid use. * </p> * * <p> * The base class implementation of measure defaults to the background size, * unless a larger size is allowed by the MeasureSpec. Subclasses should * override {@link #onMeasure(int, int)} to provide better measurements of * their content. * </p> * * <p> * If this method is overridden, it is the subclass's responsibility to make * sure the measured height and width are at least the view's minimum height * and width ({@link #getSuggestedMinimumHeight()} and * {@link #getSuggestedMinimumWidth()}). * </p> * * @param widthMeasureSpec horizontal space requirements as imposed by the parent. *The requirements are encoded with *{@link android.view.View.MeasureSpec}. * @param heightMeasureSpec vertical space requirements as imposed by the parent. *The requirements are encoded with *{@link android.view.View.MeasureSpec}. * * @see #getMeasuredWidth() * @see #getMeasuredHeight() * @see #setMeasuredDimension(int, int) * @see #getSuggestedMinimumHeight() * @see #getSuggestedMinimumWidth() * @see android.view.View.MeasureSpec#getMode(int) * @see android.view.View.MeasureSpec#getSize(int) */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
getDefaultSize方法相關
public static int getDefaultSize(int size, int measureSpec) { int result = size; //通過measureSpec得到mode和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; } //最小寬度和高度由View的Background尺寸和View的minXXX共同決定 protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
setMeasuredDimension方法 對View的成員變數measuredWidth和measuredHeight變數賦值 也就是說該方法最終決定了View的大小
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); } public boolean isLayoutRequested() { return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; } private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
至此一次最基礎的View的measure過程就完成了 但是由於View可以巢狀 所以measure是遞迴傳遞的所以ViewGroup中需要對其子類進行measure過程 measureChildren方法實質為迴圈呼叫measureChild方法
而measureChild和measureChildWithMargins的區別是後者將margin和padding也作為了子檢視的大小
一下分析measureChildWithMargins方法
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { //獲取當前子檢視的LayoutParams final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //設定子View的測量規格 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); //子view的繼續呼叫 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } //在getChildMeasureSpec中通過父View和本身的模式共同決定當前View的size public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //獲取當前父View的mode和size int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); //獲取父View的的剩餘大小 int size = Math.max(0, specSize - padding); //定義結果變數 int resultSize = 0; int resultMode = 0; //根據對應的mode做處理 //通過父View和本身的模式共同決定當前View的size 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; } //將size和mode整合為MeasureSpec模式後返回 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
layout原始碼分析
View layout整體流程與measure過程基本一樣
結論:
- 需要根據ViewGroup本身的情況討論 LinearLayout下會更看重子View的height和width 來安排對應位置 而RelativeLayout則更加關注子View的left right top bottom值 並且優先順序高於width和height 甚至在部分自定義ViewGroup中 measure可能是無用的 直接使用layout方法來設定子View的位置也可以
- ViewGroup需要實現自己的layout邏輯
- layout_XXX中的各個熟悉都是針對子View的父ViewGroup的
- 同樣使用View的getWidth()和getHeight()方法來獲取View測量的寬高 必須保證這兩個方法在onLayout流程之後被呼叫才能返回有效值
/** * Assign a size and position to a view and all of its * descendants * * <p>This is the second phase of the layout mechanism. * (The first is measuring). In this phase, each parent calls * layout on all of its children to position them. * This is typically done using the child measurements * that were stored in the measure pass().</p> * * <p>Derived classes should not override this method. * Derived classes with children should override * onLayout. In that method, they should * call layout on each of their children.</p> * * @param l Left position, relative to parent * @param t Top position, relative to parent * @param r Right position, relative to parent * @param b Bottom position, relative to parent */ //同樣註解寫的很好了分派給他和他的所有的子檢視大小和位置 @SuppressWarnings({"unchecked"}) public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } //呼叫setFrame方法把引數分別賦值於 int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; //判斷view的位置是否發生過變化 , 確定是否對當前view重新layout boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //呼叫onLayout onLayout(changed, l, t, r, b); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.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); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }
onLyayout方法
View中 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { } ViewGroup中 protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
均是空方法 後面會就LinearLayout和RelativeLayout原始碼進行分析
draw原始碼分析
View的draw流程圖如下

View draw流程
結論:
- View需要在子類中實現onDraw的過程
- 在ViewGroup中 會呼叫其子View的方法 順序與子view的新增順序一致
draw的原始碼也很長 但是官方也給出給出了draw的過程
public void draw(Canvas canvas) { ... /* * 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 ... if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) ... // Step 2, save the canvas' layers ... if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } ... // 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 ... if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } ... // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); ... }
Step 1, draw the background, if needed
// Step 1, draw the background, if needed //如果需要的話繪製背景 if (!dirtyOpaque) { drawBackground(canvas); }
private void drawBackground(Canvas canvas) { //通過xml中屬性background或者程式碼中setBackGroundColor\setBackgroundResource等方法賦值的背景drawable final Drawable background = mBackground; if (background == null) { return; } //根據layout中確定的view位置來設定背景的繪製區域 setBackgroundBounds(); // 如果需要的話使用顯示列表 //canvas.isHardwareAccelerated() 硬體加速判定 //硬體加速時會將圖層快取到GPU上 而不是重繪View的每一層 if (canvas.isHardwareAccelerated() && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) { mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); final RenderNode renderNode = mBackgroundRenderNode; if (renderNode != null && renderNode.isValid()) { setBackgroundRenderNodeProperties(renderNode); ((DisplayListCanvas) canvas).drawRenderNode(renderNode); return; } } final int scrollX = mScrollX; final int scrollY = mScrollY; //呼叫Drawable的draw方法來完成背景的繪製工作 if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } void setBackgroundBounds() { if (mBackgroundSizeChanged && mBackground != null) { mBackground.setBounds(0, 0,mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; rebuildOutline(); } }
Step 2, save the canvas' layers
// Step 2, save the canvas' layers //儲存繪製圖層 if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); }
Step 3, draw the content
// Step 3, draw the content //對View的內容進行繪製 if (!dirtyOpaque) onDraw(canvas);
/** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ //onDraw也是空方法需要子類根據自身去實現相應的 protected void onDraw(Canvas canvas) { }
Step 4, draw the children
// Step 4, draw the children //繪製其子View dispatchDraw(canvas);
/** * Called by draw to draw the child views. This may be overridden * by derived classes to gain control just before its children are drawn * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ protected void dispatchDraw(Canvas canvas) { //dispatchDraw同樣空方法 與onDraw不同的是dispatchDraw在ViewGroup中被重寫 }
ViewGroup
//dispatchDraw方法中根據子View的不同情況 包括但不只包括該View是否顯示 是否有進入或消失動畫等進行了部分的調整 protected void dispatchDraw(Canvas canvas) { ... more |= drawChild(canvas, transientChild, drawingTime); ... } protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
Step 5, draw the fade effect and restore layers
// Step 5, draw the fade effect and restore layers //繪製過度效果和恢復圖層 if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); }
Step 6, draw decorations (scrollbars)
// Step 6, draw decorations (scrollbars) //對滾動條進行繪製 onDrawScrollBars(canvas);
至此 View的繪製過程全部分析完了
【附錄】

資料圖