View繪制流程(一)
View的布局
當ViewRootImpl的performTraversals
中performMeasure
執行完成以后會接著執行performLayout
,ViewRootImpl調用performLayout
執行Window對應的View的布局。
- ViewRootImpl的performLayout。
- DecorView(FrameLayout)的layout方法。
- DecorView(FrameLayout)的onLayout方法。
- DecorView(FrameLayout)的layoutChildren方法。
- DecorView(FrameLayout)的所有子View的Layout。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
/*******部分代碼省略**********/
//View的布局
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
/*******部分代碼省略**********/
final View host = mView;
/*******部分代碼省略**********/
try {
//調用View的Layout方法進行布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
//在ViewRootImpl進行布局的期間,Window內的View自己進行requestLayout
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
//請求對該View布局,最終回調到ViewRootImpl的requestLayout進行重新測量、布局、繪制
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
/*******部分代碼省略**********/
//請求對該View布局,最終回調到ViewRootImpl的requestLayout進行重新測量、布局、繪制
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
}
layout
方法接收四個參數,這四個參數分別代表相對Parent的左、上、右、下坐標。而且還可以看見左上都為0,右下分別為上面剛剛測量的width和height。
public void layout(int l, int t, int r, int b) {
......
//實質都是調用setFrame方法把參數分別賦值給mLeft、mTop、mRight和mBottom這幾個變量
//判斷View的位置是否發生過變化,以確定有沒有必要對當前的View進行重新layout
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//需要重新layout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//回調onLayout
onLayout(changed, l, t, r, b);
......
}
......
}
類似measure過程,layout
調用了onLayout
方法,這里需要注意的是layout
方法可以被子類重寫,下面看一下View的onLayout
方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
竟然是一個空的方法。,那么就看一下ViewGroup的layout
方法。
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
//調用View的layout方法
super.layout(l, t, r, b);
} else {
mLayoutCalledWhileSuppressed = true;
}
}
本質還是調用View的
layout
方法,這里需要注意的是ViewGroup的layout方法是不能被子類重寫的。
接下來看下ViewGroup的onLayout
方法。
@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
ViewGroup的
onLayout()
方法竟然是一個抽象方法,這就是說所有ViewGroup的子類都必須重寫這個方法。所以在自定義ViewGroup控件中,onLayout
配合onMeasure
方法一起使用可以實現自定義View的復雜布局。自定義View首先調用onMeasure
進行測量,然后調用onLayout
方法動態獲取子View和子View的測量大小,然后進行layout布局。重載onLayout的目的就是安排其children在父View的具體位置,重載onLayout
通常做法就是寫一個for循環調用每一個子視圖的layout(l, t, r, b)
函數,傳入不同的參數l, t, r, b
來確定每個子視圖在父視圖中的顯示位置。
既然View的onLayout
方法為空方法,ViewGroup的onLayout
方法為抽象方法,下面以ViewGroup的子類LinearLayout為例。
public class LinearLayout extends ViewGroup {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
}
LinearLayout的
layout
過程是分Vertical和Horizontal的,這個就是xml布局的orientation
屬性設置的。
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
//計算父窗口推薦的子View寬度
final int width = right - left;
//計算父窗口推薦的子View右側位置
int childRight = width - mPaddingRight;
// Space available for child
//child可使用空間大小
int childSpace = width - paddingLeft - mPaddingRight;
//通過ViewGroup的getChildCount方法獲取ViewGroup的子View個數
final int count = getVirtualChildCount();
//獲取Gravity屬性設置
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//依據majorGravity計算childTop的位置值
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
//重點!!!開始遍歷
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//LinearLayout中其子視圖顯示的寬和高由measure過程來決定的,因此measure過程的意義就是為layout過程提供視圖顯示范圍的參考值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//獲取子View的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
//依據不同的absoluteGravity計算childLeft位置
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//通過垂直排列計算調運child的layout設置child的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
實質都是調用
setFrame
方法把參數分別賦值給mLeft、mTop、mRight和mBottom這幾個變量。
從上面分析的ViewGroup子類LinearLayout的onLayout
實現代碼可以看出,一般情況下layout
過程會參考measure
過程中計算得到的mMeasuredWidth和mMeasuredHeight來安排子View在父View中顯示的位置,但這不是必須的,measure
過程得到的結果可能完全沒有實際用處,特別是對于一些自定義的ViewGroup,其子View的個數、位置和大小都是固定的,這時候我們可以忽略整個measure
過程,只在layout
方法中傳入的4個參數來安排每個子View的具體位置。
到這里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()
這兩對方法之間的區別(上面分析measure
過程已經說過getMeasuredWidth()、getMeasuredHeight()
必須在onMeasure
之后使用才有效)。可以看出來getWidth()與getHeight()
方法必須在layout(int l, int t, int r, int b)
執行之后才有效。那我們看下View源碼中這些方法的實現吧,如下:
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
public final int getLeft() {
return mLeft;
}
public final int getRight() {
return mRight;
}
public final int getTop() {
return mTop;
}
public final int getBottom() {
return mBottom;
}
mMeasuredWidth
是一個8位的十六進制數,高兩位代表Mode 跟MEASURED_SIZE_MASK
按位&后獲得測量后的寬度。
View布局總結
View.layout
方法可被重載,ViewGroup.layout
為final的不可重載,ViewGroup.onLayout
為abstract的,子類必須重載實現自己的位置邏輯。View.onLayout
方法是一個空方法。
measure
操作完成后得到的是對每個View經測量過的measuredWidth和measuredHeight,layout
操作完成之后得到的是對每個View進行位置分配后的mLeft、mTop、mRight、mBottom
,這些值都是相對于父View來說的。
onLayout
中最終循環調用子View的setFrame
方法來設置mLeft、mTop、mRight和mBottom的值。
getMeasuredWidth()、getMeasuredHeight()
必須在onMeasure
之后使用才有效;getWidth()與getHeight()
方法必須在layout(int l, int t, int r, int b)
執行之后才有效。
View的繪制
ViewRootImpl調用performDraw
執行Window對應的View的布局。
- ViewRootImpl的
performDraw
;- ViewRootImpl的
draw
;- ViewRootImpl的
drawSoftware
;- DecorView(FrameLayout)的
draw
方法;- DecorView(FrameLayout)的
dispatchDraw
方法;- DecorView(FrameLayout)的
drawChild
方法;- DecorView(FrameLayout)的所有子View的
draw
方法;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
/*******部分代碼省略**********/
//View的繪制
private void performDraw() {
/*******部分代碼省略**********/
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
/*******部分代碼省略**********/
}
//進行繪制
private void draw(boolean fullRedrawNeeded) {
/*******部分代碼省略**********/
//View上添加的Observer進行繪制事件的分發
mAttachInfo.mTreeObserver.dispatchOnDraw();
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
/*******部分代碼省略**********/
//調用Window對應的ViewRootImpl的invalidate方法
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
/*******部分代碼省略**********/
//繪制Window
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
/**
* @return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
/*******部分代碼省略**********/
try {
/*******部分代碼省略**********/
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
//View繪制
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
/*******部分代碼省略**********/
}
return true;
}
}
由于ViewGroup沒有重寫View的draw
方法,所以直接看View.draw
方法:
public void draw(Canvas canvas) {
......
// 1. 繪制背景
......
if (!dirtyOpaque) {
drawBackground(canvas);
}
......
//2.繪制View的內容
if (!dirtyOpaque) onDraw(canvas);
//3.對當前View的所有子View進行繪制,如果當前的View沒有子View就不需要進行繪制。
dispatchDraw(canvas);
......
//4.對View的滾動條進行繪制。
onDrawScrollBars(canvas);
......
}
1.對View的背景進行繪制。
private void drawBackground(Canvas canvas) {
//獲取xml中通過android:background屬性或者代碼中setBackgroundColor()、setBackgroundResource()等方法進行賦值的背景Drawable
final Drawable background = mBackground;
......
//根據layout過程確定的View位置來設置背景的繪制區域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
......
//調用Drawable的draw()方法來完成背景的繪制工作
background.draw(canvas);
......
}
可以看出View背景是一個Drawable,繪制背景最終調用的是
Drawable.draw
2.對View的內容進行繪制。
protected void onDraw(Canvas canvas) {
}
View.onDraw
方法為空方法,ViewGroup也沒有重寫該方法。因為每個View的內容部分是各不相同的,所以需要由子類去實現具體邏輯。
3.對當前View的所有子View進行繪制,如果當前的View沒有子View就不需要進行繪制。
View.dispatchDraw()
方法是一個空方法,如果View包含子類需要重寫他,所以我們看下ViewGroup.dispatchDraw
方法源碼:
@Override
protected void dispatchDraw(Canvas canvas) {
......
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
......
for (int i = disappearingCount; i >= 0; i--) {
......
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
該方法內部會遍歷每個子View,然后調用drawChild()
方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
4.對View的滾動條進行繪制。
protected final void onDrawScrollBars(Canvas canvas) {
final ScrollabilityCache cache = mScrollCache;
if (cache != null) {
int state = cache.state;
if (state == ScrollabilityCache.OFF) {
return;
}
boolean invalidate = false;
.........
final boolean drawHorizontalScrollBar = isHorizontalScrollBarEnabled();
final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled()
&& !isVerticalScrollBarHidden();
// Fork out the scroll bar drawing for round wearable devices.
if (mRoundScrollbarRenderer != null) {
if (drawVerticalScrollBar) {
final Rect bounds = cache.mScrollBarBounds;
getVerticalScrollBarBounds(bounds, null);
mRoundScrollbarRenderer.drawRoundScrollbars(
canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds);
if (invalidate) {
invalidate();
}
}
} else if (drawVerticalScrollBar || drawHorizontalScrollBar) {
final ScrollBarDrawable scrollBar = cache.scrollBar;
if (drawHorizontalScrollBar) {
scrollBar.setParameters(computeHorizontalScrollRange(),
computeHorizontalScrollOffset(),
computeHorizontalScrollExtent(), false);
final Rect bounds = cache.mScrollBarBounds;
getHorizontalScrollBarBounds(bounds, null);
onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
bounds.right, bounds.bottom);
if (invalidate) {
invalidate(bounds);
}
}
if (drawVerticalScrollBar) {
scrollBar.setParameters(computeVerticalScrollRange(),
computeVerticalScrollOffset(),
computeVerticalScrollExtent(), true);
final Rect bounds = cache.mScrollBarBounds;
getVerticalScrollBarBounds(bounds, null);
onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
bounds.right, bounds.bottom);
if (invalidate) {
invalidate(bounds);
}
}
}
}
}
可以看見其實任何一個View都是有(水平垂直)滾動條的,只是一般情況下沒讓它顯示而已。
View的invalidate和postInvalidate方法源碼分析
View中的invalidate
方法有很多的重載,最終都會調用invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate)
方法(ViewGroup沒有重寫這些方法)
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
......
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
//設置刷新區域
damage.set(l, t, r, b);
//傳遞調運Parent ViewGroup的invalidateChild方法
p.invalidateChild(this, damage);
}
......
}
View.invalidateInternal
方法實質是將要刷新區域直接傳遞給了父ViewGroup的invalidateChild
方法,在該方法中,調用父View的invalidateChild
,這是一個從當前向上級父View回溯的過程,每一層的父View都將自己的顯示區域與傳入的刷新Rect做交集 。所以我們看下ViewGroup的invalidateChild
方法,源碼如下:
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
......
do {
......
//循環層層上級調運,直到ViewRootImpl會返回null
parent = parent.invalidateChildInParent(location, dirty);
......
} while (parent != null);
}
這個過程最后傳遞到
ViewRootImpl的invalidateChildInParent
方法結束,所以我們看下ViewRootImpl的invalidateChildInParent
方法,如下:
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
......
//View調運invalidate最終層層上傳到ViewRootImpl后最終觸發了該方法
scheduleTraversals();
......
return null;
}
最終調用了
scheduleTraversals
方法,該方法會調用ViewRootImpl.performTraversals
方法,重新繪制。
invalidate
該方法只能在UI Thread中執行,其他線程中需要使用postInvalidate
方法
postInvalidate
方法最終會調用ViewRootImpl類的dispatchInvalidateDelayed
方法,源碼如下:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
ViewRootImpl類的Handler發送了一條MSG_INVALIDATE
消息,繼續追蹤這條消息的處理可以發現:
public void handleMessage(Message msg) {
......
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
......
}
......
}
最終在UI線程中調用了View的
invalidate
方法。
直接調用invalidate
方法.請求重新draw,但只會繪制調用者本身。因為其他的View狀態沒有變化的話,是不會執行對應的繪制方法的。