Android View 原始碼解析(二) – LayoutInflater
現在開始分析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流程之後被呼叫才能返回有效值。
/**
* <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需要在子類中實現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或者程式碼中setBackGroundColorsetBackgroundResource等方法賦值的背景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的繪製過程全部分析完了