一、繪製流程原始碼路徑
1、Activity載入ViewRootImpl
ActivityThread.handleResumeActivity()
--> WindowManagerImpl.addView(decorView, layoutParams)
--> WindowManagerGlobal.addView()
2、ViewRootImpl啟動View樹的遍歷
ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals()(performMeasure、performLayout、performDraw)
二、View繪製流程
1、measure
(1)MeasureSpec是什麼?
重寫過onMeasure()方法都知道,測量需要用到MeasureSpec類獲取View的測量模式和大小,那麼這個類是怎樣儲存這兩個資訊呢?
留心觀察的話會發現,onMeasure方法的兩個引數實際是32位int型別資料,即:
00 000000 00000000 00000000 00000000
而其結構為 mode + size ,前2位為mode,而後30位為size。
==> getMode()方法(measureSpec --> mode):
private static final int MODE_SHIFT = 30;
// 0x3轉換為二進位制即為:11
// 左移30位後:11000000 00000000 00000000 00000000
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static int getMode(int measureSpec) {
// 與MODE_MASK按位與運算後,即將低30位清零,結果為mode左移30位後的值
return (measureSpec & MODE_MASK);
}
getSize()方法同理。
==> makeMeasureSpec()方法(mode + size --> measureSpec):
public static int makeMeasureSpec(
@IntRange(from = 0,
to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
這裡解釋一下,按位或左側為size的高2位清零後的結果,右側為mode的低30位清零後的結果,兩者按位或運算的結果正好為高2位mode、低30位size,例:
01000000 00000000 00000000 00000000 |
00001000 00001011 11110101 10101101 =
01001000 00001011 11110101 10101101
二進位制計算規則可參考:https://www.cnblogs.com/joahyau/p/6420619.html
==> 測量模式:
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
UNSPECIFIED:父容器不對View作任何限制,系統內部使用。
EXACTLY:精確模式,父容器檢測出View大小,即為SpecSize;對應LayoutParams中的match_parent和指定大小的情況。
AT_MOST:最大模式,父容器指定可用大小,View的大小不能超出這個值;對應wrap_content。
(2)ViewGroup的測量流程
回到ViewRootImpl的performMeasure方法,這裡傳入的引數為頂層DecorView的測量規格,其測量方式為:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
match_parent和具體數值大小為EXACTLY模式,wrap_content則為AT_MOST模式。
往下走,performMeasure方法中呼叫了DecorView的onMeasure方法,而DecorView繼承自FrameLayout,可以看到FL的onMeasure方法中呼叫了measureChildWithMargins方法,並傳入自身的測量規格:
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);
}
即測量子控制元件的大小,測量規則詳情可看getChildMeasureSpec方法,總結如下:
childLayoutParams\parentSpecMode | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp | EXACTLY/childSize | EXACTLY/childSize | EXCATLY/childSize |
match_parent | EXACTLY/parentSize | AT_MOST/parentSize | UNSPECIFIED/0 |
wrap_content | AT_MOST/parentSize | AT_MOST/parentSize | UNSPECIFIED/0 |
回到onMeasure方法,測完子控制元件之後,ViewGroup會經過一些計算,得出自身大小:
// 加上padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// 檢查是否小於最小寬度、最小高度
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 檢查Drawable的最小高度和寬度
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
綜上,ViewGroup的測量需要先測量子View的大小,而後結合padding等屬性計算得出自身大小。
(3)View的測量流程
View.performMeasure()
-->onMeasure(int widthMeasureSpec, int heightMeasureSpec)
-->setMeasuredDimension(int measuredWidth, int measuredHeight)
-->setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)
可以看到setMeasuredDimensionRaw()方法:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
// 儲存測量結果
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
// 設定測量完成的標誌位
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
View不需要考慮子View的大小,根據內容測量得出自身大小即可。
另外,View中的onMeasure方法中呼叫到getDefaultSize方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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 = specSize;
break;
}
return result;
}
這裡看到精確模式和最大模式,最終測量的結果都是父容器的大小,即佈局中的wrap_content、match_parent以及數值大小效果都一樣,這也就是自定義View一定要重寫onMeasure方法的原因。
2、layout
佈局相對測量而言要簡單許多,從ViewRootImpl的performLayout方法出發,可以看到其中呼叫了DecorView的layout方法:
// 實則為DecorView的left, top, right, bottom四個資訊
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
進入layout方法,發現l、t、r、b被傳遞到了setFrame方法中,並設定給了成員變數:
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
所以,佈局實際為呼叫View的layout方法,設定自身的l、t、r、b值。另外,layout方法中往下走,可以看到呼叫了onLayout方法,進入後發現為空方法。因而檢視FrameLayout的onLayout方法:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
// 省略
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 省略
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
可以看到,進行一系列計算後,呼叫了child的layout方法,對子控制元件進行佈局,同時子控制元件又會繼續往下對自己的子控制元件佈局,從而實現遍歷。
綜上,佈局實際為呼叫layout方法設定View位置,ViewGroup則需要另外實現onLayout方法擺放子控制元件。
3、draw
(1)繪製過程入口
ViewRootImpl.performDraw()
-->ViewRootImpl.draw()
-->ViewRootImpl.drawSoftware()
-->View.draw()
(2)繪製步驟
進入到View的draw方法中,可以看到以下一段註釋:
/*
* 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)
*/
結合draw方法的原始碼,繪製過程的關鍵步驟如下:
==> 繪製背景:drawBackground(canvas)
==> 繪製自己:onDraw(canvas)
==> 繪製子view:dispatchDraw(canvas)
==> 繪製滾動條、前景等裝飾:onDrawForeground(canvas)