概述
ViewTree的繪制党远,起點是ViewRootImpl的performTraversals方法
private void performTraversals() {
...
windowSizeMayChange |= measureHierarchy(host, lp, res,desiredWindowWidth, desiredWindowHeight);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
performDraw();
...
}
View系統(tǒng)的繪制流程會從ViewRootImpl的performTraversals()方法中開始, 每一個視圖的繪制過程都必須經(jīng)歷三個最主要的階段onMeasure()宾添、onLayout()和onDraw()。
measure
measure函數(shù)的作用是為整個View樹計算實際的大小, 設置每個View對象的布局大小(“窗口”大小)川抡。實際對應屬性就是View中的mMeasuredHeight(高)和mMeasureWidth(寬)凯亮。方法中參數(shù)widthMeasureSpec和heightMeasureSpec, 這兩個值分別用于確定視圖的寬度和高度的規(guī)格和大小边臼。
MeasureSpec的值由specSize和specMode共同組成的, 其中specSize記錄的是大小, specMode記錄的是規(guī)格。
specMode | 含義 |
---|---|
EXACTLY | 表示父視圖希望子視圖的大小應該是由specSize的值來決定的假消。子元素將被限定在給定的邊界里而忽略它本身大小 |
AT_MOST | 表示子視圖最多只能是specSize中指定的大小, 開發(fā)人員應該盡可能小得去設置這個視圖, 并且保證不會超過specSize |
UNSPECIFIED | 表示開發(fā)人員可以將視圖按照自己的意愿設置成任意的大小, 沒有任何限制柠并。這種情況比較少見, 不太會用到 |
View的measure方法代碼:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchingSize = isExactly &&
getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&
getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
if (forceLayout || !matchingSize &&
(widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec)) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
...
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
...
}
View的onMeasure默認實現(xiàn):
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
子View不可重寫的setMeasureDimension方法
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
...
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
...
}
measure()這個方法是final的, 因此我們無法在子類中去重寫這個方法, 說明Android是不允許我們改變View的measure框架的。measure方法中會調(diào)用onMeasure方法富拗。
FrameLayout的onMeasure實現(xiàn):
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
...
}
}
...
}
ViewGroup的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);
}
layout
private void performLayout(WindowManager.LayoutParams lp,
int desiredWindowWidth, int desiredWindowHeight) {
...
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
傳入測量出的寬度臼予、高度。
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;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
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;
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
layout()方法接收四個參數(shù), 分別代表著左啃沪、上粘拾、右、下的坐標, 當然這些坐標是相對于當前視圖的父視圖而言的创千。在layout()方法中, 首先會調(diào)用setFrame()方法來判斷視圖的大小是否發(fā)生過變化, 以確定有沒有必要對當前的視圖進行重繪, 同時還會在這里把傳遞過來的四個參數(shù)分別賦值給mLeft缰雇、mTop入偷、mRight和mBottom這幾個變量。View中的onLayout()方法就是一個空方法, 因為onLayout()過程是為了確定視圖在布局中所在的位置, 而這個操作應該是由布局來完成的, 即父視圖決定子視圖的顯示位置械哟。
ViewGroup的onLayout:
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
所有ViewGroup的子類都必須重寫這個方法盯串。在onLayout()過程結束后, 我們就可以調(diào)用getWidth()方法和getHeight()方法來獲取視圖的寬高了。
getWidth()方法和getMeasureWidth()方法到底有什么區(qū)別
getMeasureWidth()方法在measure()過程結束后就可以獲取到了, 而getWidth()方法要在layout()過程結束后才能獲取到戒良。另外, getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設置的, 而getWidth()方法中的值則是通過視圖右邊的坐標減去左邊的坐標計算出來的体捏。
draw
ViewRootImpl首先會判斷是否要硬件加速。沒有硬件加速糯崎,走drawSoftware方法:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
...
// Draw with software renderer.
final Canvas canvas;
...//設置canvas
mView.draw(canvas);
...
}
View中的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
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);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
...
}
protected void onDraw(Canvas 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).
*/
protected void dispatchDraw(Canvas canvas) {
}
View的onDraw為空方法, 因為每個視圖的內(nèi)容部分肯定都是各不相同的, 這部分的功能需交給子類去實現(xiàn)几缭。dispatchDraw這一步的作用是對當前視圖的所有子視圖進行繪制。但如果當前的視圖沒有子視圖, 那么也就不需要進行繪制了沃呢。因此你會發(fā)現(xiàn)View中的dispatchDraw()方法又是一個空方法年栓,而ViewGroup的dispatchDraw()方法中就會有具體的繪制代碼。onDrawScrollBars 是對視圖的滾動條進行繪制薄霜。
其他
窗口的UI最終是需要通過SurfaceFlinger服務來統(tǒng)一渲染的, 而SurfaceFlinger服務在渲染窗口的UI之前, 需要計算基于各個窗口的Z軸位置來計算它們的可見區(qū)域某抓。而WindowManagerService服務就是負責計算好每一個窗口的Z軸位置之后, 還需要將它們設置到SurfaceFlinger服務中去, 以便SurfaceFlinger服務可以正確地渲染每一個窗口的UI。