從前面的WindowManager文章中,Activity的窗口創建流程可以看到,在ActivityThread.handleResumeActivity方法中回調onResume,然后調用wm.addView(decor, l)添加DecorView,同時創建ViewRootImpl對象,ViewRootImpl用于管理view的繪制和事件的分發,由此可以看出一個Activity對應一個ViewRootImpl 和一個DecorView根視圖。而View的繪制流程從ViewRoot的performTraversals方法開始,經過measure、layout、draw三個過程分別完成計算大小、位置以及繪制。
一、measure過程
ViewRootImpl中的performTraversals() 方法調用performMeasure() 用來測量view的大小。
下面我們從計算根視圖DecorView的大小開始。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
這里mView 是前面setView傳過來的DecorView,調用到父類的View的measure函數。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
... ...
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
//forceLayout目前view正在執行布局操作,needsLayout當前視圖大小跟之前不一樣時需要重新計算
if (forceLayout || needsLayout) {
// 清空flag,PFLAG_MEASURED_DIMENSION_SET 是控件onMeasure中調用setMeasuredDimension()時設置的flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 真正的測量控件大小,調用當前view的onMeasure 方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// onMeasure 中沒有調用 setMeasuredDimension(),將會拋出如下異常
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
//保存當前值
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
View.java 中的measure() 方法是final類型,子類不能復寫。measure方法用于提供父View對長寬的約束信息,控制什么時候view需要被重新計算,真正的計算View尺寸是在onMeasure中進行的。
或者說View的measure是View的測量框架,用戶不可以修改其邏輯,具體的大小計算在各個空間的onMeasure方法中實現。
再看DecorView的onMeasure
/frameworks/base/core/java/com/android/internal/policy/DecorView.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
... ...
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
... ...
}
DecorView.onMeasure主要對AT_MOST情況時的長寬作了調整,因為此時是Activity,不會走到AT_MOST條件,直接調用父類FrameLayout.onMeasure
/frameworks/base/core/java/android/widget/FrameLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//得到view個數
int count = getChildCount();
//計算所有子View中的最大寬高
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//這里會調用子View中的measure方法,實現遞歸的計算所有視圖的大小
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
... ...
//把寬高信息設置到View中
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
... ...
}
FrameLayout的主要工作:
1.調用子View的measureChildWithMargins方法,這個函數里會調用View.measure方法,遞歸計算子View的寬高信息。
2.計算出合適的寬高,然后調用setMeasuredDimension設置測量結果
思考: 定義為wrap_content 的view的大小計算過程?
getChildMeasureSpec用于產生子view測量規范,當子view為wrap_content 時,父view是Exactly 或者 At_Most 都會給子view的spec 為:mode At_Most,size 父view大小,因為此時子view大小不確定。
(1)如果是view(TextView),父view的spec mode為 At_Most,size 為父view大小, 取父view的size 和子view的size的最小值作為最終長寬,子view size 可以根據父view的 padding ,子view的margin以及內容大小(textsize 背景圖片等)確定。
(2)如果是viewgroup(LinearLayout 等),調用其子view.measure,然后調用到子view.onMeasure ,如果子view大小確定,傳給子view的spec 為 Exactly + 子view大小(參考getChildMeasureSpec源碼),子view 的onMeasure方法計算出確定值后,回到當前view可以得到子view大小,根據子view大小,以及父view給的at_most ,兩者取較小,即為當前view大小。如果其子view還是viewgroup,則繼續遞歸調用view.measure,控件.onMeasure去計算各個子view真實大小。
二、layout過程
ViewRootImpl.performLayout是計算各個view位置的開始。
/frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
... ...
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
//layout四個參數l,t,r,b分別代表該view相對于父view的位置
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
... ...
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
這里host是DecorView, DecorView沒有實現layout方法, 直接調用到View.layout。
/frameworks/base/core/java/android/view/View.java
public void layout(int l, int t, int r, int b) {
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//調用該View的onLayout做進一步處理
onLayout(changed, l, t, r, b);
}
}
/frameworks/base/core/java/com/android/internal/policy/DecorView.java
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
... ...
}
直接調用父類FrameLayout.onLayout
/frameworks/base/core/java/android/widget/FrameLayout.java
@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();
//改為
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//根據子View的外邊距及Gravity信息,計算子View的位置
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//onMeasure時得到的view高寬
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//最終調用child.layout遍歷布局各個子View
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
根據當前view的位置內邊距和子view的寬高及外邊距確定子View的位置,然后再調用View.layout ,遞歸實現所有子View位置的計算。
三、draw過程
同樣從ViewRootImpl的performDraw方法開始View的繪制draw 過程
/frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
... ...
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
主要調用ViewRootImpl的draw方法
private void draw(boolean fullRedrawNeeded) {
//mSurface表示繪圖界面,所有view都繪制在上面
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
if (DEBUG_FPS) {
trackFPS();
}
//Jit相關
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
//判斷當前界面是否處于滾動狀態
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
//如果當前Y軸位置跟之前不相等則調用onRootViewScrollYChanged 通知view發生滾動
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
//只有DecorView實現了接口RootViewSurfaceTaker
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
//窗口是否處于縮放狀態
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
//矩形繪制區域
final Rect dirty = mDirty;
//如果應用使用SurfaceView自己在渲染線程繪制,那么清空dirty,無需繼續繪制
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return;
}
//設置繪制區域的大小
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
... ...
//重新繪制UI
//需要硬件加速
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
//調用drawSoftware
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
//請求下一次繪制
scheduleTraversals();
}
}
/frameworks/base/core/java/android/view/ViewRootImpl.java
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
... ...
//獲得Canvas對象并鎖定畫布
canvas = mSurface.lockCanvas(dirty);
... ...
}
try {
... ...
try {
... ...
//調用DecorView 的draw方法在畫布上繪制UI
mView.draw(canvas);
... ...
}
} finally {
try {
//調用Surface的unlockCanvasAndPost方法請求SurfaceFlinger服務渲染畫布里面圖形緩沖區,讓UI顯示出來
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
}
return true;
}
繼續看下DecorView.draw
/frameworks/base/core/java/com/android/internal/policy/DecorView.java
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
主要調用父類View.draw 方法,View的draw主要完成以下工作:
繪制背景
如果需要,保存畫布層級用來繪制當前視圖在滑動時的邊框漸變效果
繪制當前視圖內容
繪制他的孩子
如果需要,繪制當前視圖在滑動時的漸變效果并存儲圖層。
繪制滾動條等
/frameworks/base/core/java/android/view/View.java
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 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);
}
// 當前視圖不是出于滑動狀態則跳過第2、5步
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);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
... ...
// Step 2, save the canvas' layers
//如果當前視圖背景顏色為0,創建額外的圖層繪制滑動效果,否則使用這個背景顏色作為漸變效果
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
//調用當前視圖的onDraw方法
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
//繪制孩子視圖
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
//繪制上下左右四個方向滑動的漸變效果
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
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);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
//繪制scrollbars
onDrawForeground(canvas);
}
Step 3 繪制當前視圖這里調用DecorView 的onDraw 方法,DecorView.onDraw直接調用父類onDraw,最后調用到View.onDraw,沒有特別要繪制的內容。下面重點看下Step 4繪制各個子View過程,只有容器視圖才需要繪制子視圖,所以View中的dispatchDraw 方法是空的,直接看下ViewGroup中的dispatchDraw方法:
/frameworks/base/core/java/android/view/ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
//需要繪制動畫
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
//遍歷子View并給子View設置動畫參數
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
//LayoutAnimation用于給ViewGroup里面子View設置動畫效果
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
//啟動動畫
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
//通知注冊了AnimationListener監聽器的View開始動畫
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
clipSaveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
// We will draw our child's animation, let's reset the flag
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
final long drawingTime = getDrawingTime();
if (usingRenderNodeProperties) canvas.insertReorderBarrier();
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
int transientIndex = transientCount != 0 ? 0 : -1;
// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
//遍歷子View,如果子View可見或者正在執行動畫,那么調用drawChild繪制子View
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
if (preorderedList != null) preorderedList.clear();
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
if (usingRenderNodeProperties) canvas.insertInorderBarrier();
if (debugDraw()) {
onDebugDraw(canvas);
}
if (clipToPadding) {
canvas.restoreToCount(clipSaveCount);
}
// mGroupFlags might have been updated by drawChild()
flags = mGroupFlags;
//重新繪制UI請求,會重新調用View繪制三部曲performMeasure,performLayout,performDraw
if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);
}
//動畫繪制完成,并通知動畫監聽器
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
mLayoutAnimationController.isDone() && !more) {
// We want to erase the drawing cache and notify the listener after the
// next frame is drawn because one extra invalidate() is caused by
// drawChild() after the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
@Override
public void run() {
notifyAnimationListener();
}
};
post(end);
}
}
- dispatchDraw 中調用drawChild去繪制子View
/frameworks/base/core/java/android/view/ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
調用View.draw用同樣的方法繪制子View,實現了遞歸的繪制過程。
- dispatchDraw動畫繪制結束后通知監聽器動畫結束,注意這里使用了View.post, 結合注釋目的是等待下一幀繪制完成后再檫除繪制緩存和通知動畫監聽器。那么看下為什么調用View.post 可以達到這個目的。
/frameworks/base/core/java/android/view/View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
post中分兩種情況,第一種是 mAttachInfo不為null,直接post到mAttachInfo.mHandler所在線程,第二種放到一個消息隊列中等待執行。
那么我們首先看下什么情況下mAttachInfo才不為null,mAttachInfo在dispatchAttachedToWindow方法中賦值。
/frameworks/base/core/java/android/view/View.java
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
... ...
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
... ...
}
dispatchAttachedToWindow 在ViewRootImpl.java 的 performTraversals方法中被調用,在
/frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() {
... ...
if (mFirst) {
... ...
host.dispatchAttachedToWindow(mAttachInfo, 0);
... ...
}
... ...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
... ...
performLayout(lp, mWidth, mHeight);
... ...
performDraw();
... ...
}
performTraversals前面第一個時序圖中可以看到,在繪制界面時會被調用,
所以如果是第一次繪制則調用dispatchAttachedToWindow,因為一個Activity所有View執行完measure和layout后,mFirst才設為false。然后再看下mAttachInfo.mHandler屬于哪個線程,mAttachInfo在ViewRootImpl 構造函數中初始化,mHandler是ViewRootImpl的handler,顯然是運行在主線程。
/frameworks/base/core/java/android/view/ViewRootImpl.java
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
final ViewRootHandler mHandler = new ViewRootHandler();
前面第一個流程圖中可以看出,ActivityThread.handleResumeActivity 時會創建ViewRootImpl 對象,并調用setView觸發繪制流程,所以此時AttachInfo才不為null, 如果在Activity的onCreate過程中,就調用View.post那么AttachInfo為空,回到前面View.post方法中的mAttachInfo為null的情況:
/frameworks/base/core/java/android/view/View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
getRunQueue 得到HandlerActionQueue對象
/frameworks/base/core/java/android/view/View.java
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
/frameworks/base/core/java/android/view/HandlerActionQueue.java
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
... ...
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
}
下面再看下HandlerActionQueue.post方法,直接調用postDelayed方法,然后將我們的Runnable對象封裝成HandlerAction對象,放到數組mActions中,所以View.post方法中的getRunQueue().post(action)只是把Runnable放到了消息隊列中,真正執行需要調用HandlerActionQueue.executeActions方法,這個方法在dispatchAttachedToWindow中調用,而dispatchAttachedToWindow又在ViewRootImpl.performTraversals中被調用,performTraversals從一個流程圖中可以看到是Choreographer類 post 的TraversalRunnable到主線程的Handler執行的,所以我們調用View.post放在消息消息隊列中的Runnable會在performTraversals中所有操作執行完再取出被執行。
這就是為什么在onCreate 里面我們使用View.post 可以得到控件的高寬,因為View.post里面的Runnable會在performMeasure,performLayout之后執行,即使View.dispatchAttachedToWindow在performMeasure,performLayout之前就被調用了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView)findViewById(R.id.text);
Log.d(TAG,"width="+text.getMeasuredWidth() + " ,height=" + text.getMeasuredHeight());
text.post(new Runnable() {
@Override
public void run() {
Log.d(TAG,"post width="+text.getMeasuredWidth() + " ,height=" + text.getMeasuredHeight());
}
});
打印log:
06-01 14:35:38.800 3986-3986/com.example.srct.hdpi D/MainActivity: width=0 ,height=0
06-01 14:35:38.940 3986-3986/com.example.srct.hdpi D/MainActivity: post width=300 ,height=52
回到前面的dispatchDraw使用View.post 去通知監聽器動畫結束,可以保證在下一幀繪制完成再去通知動畫結束的原因就分析完了~~
四、應用實例
最近寫了分詞功能的自定義控件,其中實現控件的onMeasure和onLayout方法,完成了以下的布局效果:
代碼連接: https://github.com/MissMidou/Midou_BigBang
參考文章
- 《深入淺出Android源代碼》