Android的View繪製是從根節點(Activity是DecorView)開始,他是一個自上而下的過程。View的繪製經歷三個過程:Measure、Layout、Draw。基本流程如下圖:
performTraversals函數,具體的可以參考一下源代碼:
- private void performTraversals() {
- final View host = mView;
- ...
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- ...
- host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
- ...
- draw(fullRedrawNeeded);
- }
1、Measure過程
Measure過程是計算視圖大小,View中視圖measure過程相關的方法主要有三個:
- public final void measure(int widthMeasureSpec, int heightMeasureSpec)
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
measure調用onMeasure,onMeasure調用setMeasureDimension,measure,setMeasureDimension是final類型,view的子類不需要重寫,onMeasure在view的子類中重寫。
measure函數:
- public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
- if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
- widthMeasureSpec != mOldWidthMeasureSpec ||
- heightMeasureSpec != mOldHeightMeasureSpec) {
- // first clears the measured dimension flag
- mPrivateFlags &= ~MEASURED_DIMENSION_SET;
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
- }
- // measure ourselves, this should set the measured dimension flag back
- onMeasure(widthMeasureSpec, heightMeasureSpec);
- // flag not set, setMeasuredDimension() was not invoked, we raise
- // an exception to warn the developer
- if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
- throw new IllegalStateException("onMeasure() did not set the"
- + " measured dimension by calling"
- + " setMeasuredDimension()");
- }
- mPrivateFlags |= LAYOUT_REQUIRED;
- }
- mOldWidthMeasureSpec = widthMeasureSpec;
- mOldHeightMeasureSpec = heightMeasureSpec;
- }
onMeasure函數:
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
重寫onMeasure時,要調用setMeasuredDimension或者super.onMeasure來設置自身的mMeasuredWidth和mMeasuredHeight,否則,就會拋出異常.
setMeasuredDimension函數,用來設置view的大小:
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
- mPrivateFlags |= MEASURED_DIMENSION_SET;
- }
再看一下onMeasure的getDefaultSize函數:
- 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;
- }
這裏用引入了MeasureSpec類:
- public static class MeasureSpec {
- private static final int MODE_SHIFT = 30;
- private static final int MODE_MASK = 0x3 << MODE_SHIFT;
- 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;
- public static int makeMeasureSpec(int size, int mode) {
- return size + mode;
- }
- public static int getMode(int measureSpec) {
- return (measureSpec & MODE_MASK);
- }
- public static int getSize(int measureSpec) {
- return (measureSpec & ~MODE_MASK);
- }
- }
MODE_MASK爲30爲長度的二進制數,前兩位標示Mode,後面的標示Size。MeasureSpec有三種模式分別是UNSPECIFIED, EXACTLY和AT_MOST。
EXACTLY表示父視圖希望子視圖的大小應該是由specSize的值來決定的,系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。
AT_MOST表示子視圖最多隻能是specSize中指定的大小,開發人員應該儘可能小得去設置這個視圖,並且保證不會超過specSize。系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。
UNSPECIFIED表示開發人員可以將視圖按照自己的意願設置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。
widthMeasureSpec和heightMeasureSpec決定了Mode和Size的值,widthMeasureSpec和heightMeasureSpec來自父視圖,這兩個值都是由父視圖經過計算後傳遞給子視圖的,說明父視圖會在一定程度上決定子視圖的大小。但是最外層的根視圖,它的widthMeasureSpec和heightMeasureSpec又是從哪裏得到的呢?這就需要去分析ViewRoot中的源碼了,觀察performTraversals()方法可以發現如下代碼:
- childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
- childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
可以看到,這裏調用了getRootMeasureSpec()方法去獲取widthMeasureSpec和heightMeasureSpec的值,注意方法中傳入的參數,其中lp.width和lp.height在創建ViewGroup實例的時候就被賦值了,它們都等於MATCH_PARENT。然後看下getRootMeasureSpec()方法中的代碼,如下所示:
- private 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;
- }
可以看到,這裏使用了MeasureSpec.makeMeasureSpec()方法來組裝一個MeasureSpec,當rootDimension參數等於MATCH_PARENT的時候,MeasureSpec的specMode就等於EXACTLY,當rootDimension等於WRAP_CONTENT的時候,MeasureSpec的specMode就等於AT_MOST。並且MATCH_PARENT和WRAP_CONTENT時的specSize都是等於windowSize的,也就意味着根視圖總是會充滿全屏的。
Measure是一個複雜的過程,因爲一個佈局中一般都會包含多個子視圖,每個視圖都需要經歷一次measure過程。ViewGroup中定義了一個measureChildren()方法來去測量子視圖的大小,如下所示:
- protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
- final int size = mChildrenCount;
- final View[] children = mChildren;
- for (int i = 0; i < size; ++i) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
- measureChild(child, widthMeasureSpec, heightMeasureSpec);
- }
- }
- }
這裏會去遍歷當前佈局下的所有子視圖,然後逐個調用measureChild()方法來測量相應子視圖的大小:
- protected void measureChild(View child, int parentWidthMeasureSpec,
- int parentHeightMeasureSpec) {
- final LayoutParams lp = child.getLayoutParams();
- final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- mPaddingLeft + mPaddingRight, lp.width);
- final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
- mPaddingTop + mPaddingBottom, lp.height);
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
從這裏我們可以看到視圖的大小是由父視圖和子視圖共同決定的。子佈局裏面的android:layout_width和android:layout_height只是期望值,父View大小最終是由DecorView決定。父視圖提供尺寸大小的一個能力,子視圖最終尺寸與父視圖能力、子視圖期望的關係如下:
父視圖能力尺寸 |
子視圖期望尺寸 |
子視圖最終允許尺寸 |
EXACTLY + Size1 |
EXACTLY + Size2 |
EXACTLY + Size2 |
EXACTLY + Size1 |
fill_parent/match_parent |
EXACTLY+Size1 |
EXACTLY + Size1 |
wrap_content |
AT_MOST+Size1 |
AT_MOST+Size1 |
EXACTLY + Size2 |
EXACTLY+Size2 |
AT_MOST+Size1 |
fill_parent/match_parent |
AT_MOST+Size1 |
AT_MOST+Size1 |
wrap_content |
AT_MOST+Size1 |
UNSPECIFIED+Size1 |
EXACTLY + Size2 |
EXACTLY + Size2 |
UNSPECIFIED+Size1 |
fill_parent/match_parent |
UNSPECIFIED+0 |
UNSPECIFIED+Size1 |
wrap_content |
UNSPECIFIED+0 |
關於視圖的measure過程可以閱讀以下LinearLayout源碼,這樣可以更清楚的瞭解過程。
2、Layout過程
measure過程確定視圖的大小,而layout過程確定視圖的位置。loyout是從view的layout方法開始的:
- public void layout(int l, int t, int r, int b) {
- int oldL = mLeft;
- int oldT = mTop;
- int oldB = mBottom;
- int oldR = mRight;
- boolean changed = 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;
- }
函數中參數l、t、r、b是指view的左、上、右、底的位置,這幾個參數是父視圖傳入的,而根視圖中參數是由performTraversals()方法傳入的。
- host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
layout中調用了onLayout方法,在view中onLayout方法是一個空函數,他需要其子類實現。
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- }
我們關注一下LinearLayout:
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- if (mOrientation == VERTICAL) {
- layoutVertical();
- } else {
- layoutHorizontal();
- }
- }
- void layoutVertical() {
- final int paddingLeft = mPaddingLeft;
- int childTop;
- int childLeft;
- // Where right end of child should go
- final int width = mRight - mLeft;
- int childRight = width - mPaddingRight;
- // Space available for child
- int childSpace = width - paddingLeft - mPaddingRight;
- final int count = getVirtualChildCount();
- final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
- final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
- switch (majorGravity) {
- case Gravity.BOTTOM:
- // mTotalLength contains the padding already
- childTop = mPaddingTop + mBottom - mTop - mTotalLength;
- break;
- // mTotalLength contains the padding already
- case Gravity.CENTER_VERTICAL:
- childTop = mPaddingTop + (mBottom - mTop - 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) {
- final int childWidth = child.getMeasuredWidth();
- final int childHeight = child.getMeasuredHeight();
- 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);
- 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;
- setChildFrame(child, childLeft, childTop + getLocationOffset(child),
- childWidth, childHeight);
- childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
- i += getChildrenSkipCount(child, i);
- }
- }
- }
layout設置了view的位置,還設置了子視圖位置,layoutHorizontal()方法中調用了setChildFrame方法:
- private void setChildFrame(View child, int left, int top, int width, int height) {
- child.layout(left, top, left + width, top + height);
- }
從上面看出,layout也是一個自上而下的過程,先設置父視圖位置,在循環子視圖,父視圖位置一定程度上決定了子視圖位置。
3、Draw過程
draw過程調用順序在measure()和layout()之後,同樣的,performTraversals()發起的draw過程最終會調用到mView的draw()函數,這裏的mView對於Activity來說就是PhoneWindow.DecorView。看一下view類的draw方法:
- 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) {
- final Drawable background = mBackground;
- if (background != null) {
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
- if (mBackgroundSizeChanged) {
- background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
- mBackgroundSizeChanged = false;
- }
- if ((scrollX | scrollY) == 0) {
- background.draw(canvas);
- } else {
- canvas.translate(scrollX, scrollY);
- background.draw(canvas);
- canvas.translate(-scrollX, -scrollY);
- }
- }
- }
- // 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);
- // we're done...
- return;
- }
- /*
- * Here we do the full fledged routine...
- * (this is an uncommon case where speed matters less,
- * this is why we repeat some of the tests that have been
- * done above)
- */
- boolean drawTop = false;
- boolean drawBottom = false;
- boolean drawLeft = false;
- boolean drawRight = false;
- float topFadeStrength = 0.0f;
- float bottomFadeStrength = 0.0f;
- float leftFadeStrength = 0.0f;
- float rightFadeStrength = 0.0f;
- // Step 2, save the canvas' layers
- int paddingLeft = mPaddingLeft;
- final boolean offsetRequired = isPaddingOffsetRequired();
- if (offsetRequired) {
- paddingLeft += getLeftPaddingOffset();
- }
- int left = mScrollX + paddingLeft;
- int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
- int top = mScrollY + getFadeTop(offsetRequired);
- int bottom = top + getFadeHeight(offsetRequired);
- if (offsetRequired) {
- right += getRightPaddingOffset();
- bottom += getBottomPaddingOffset();
- }
- final ScrollabilityCache scrollabilityCache = mScrollCache;
- final float fadeHeight = scrollabilityCache.fadingEdgeLength;
- int length = (int) fadeHeight;
- // clip the fade length if top and bottom fades overlap
- // overlapping fades produce odd-looking artifacts
- if (verticalEdges && (top + length > bottom - length)) {
- length = (bottom - top) / 2;
- }
- // also clip horizontal fades if necessary
- if (horizontalEdges && (left + length > right - length)) {
- length = (right - left) / 2;
- }
- if (verticalEdges) {
- topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
- drawTop = topFadeStrength * fadeHeight > 1.0f;
- bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
- drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
- }
- if (horizontalEdges) {
- leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
- drawLeft = leftFadeStrength * fadeHeight > 1.0f;
- rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
- drawRight = rightFadeStrength * fadeHeight > 1.0f;
- }
- saveCount = canvas.getSaveCount();
- 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
- 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);
- 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);
- 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);
- 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);
- canvas.drawRect(right - length, top, right, bottom, p);
- }
- canvas.restoreToCount(saveCount);
- // Step 6, draw decorations (scrollbars)
- onDrawScrollBars(canvas);
- }
draw方法分成了6個步驟:
- /*
- * 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 view's content步驟調用了onDraw方法,子類中實現onDraw方法。
第四步,Draw children步驟使用的dispatchDraw方法,這個方法在ViewGroup中有實現。
View或ViewGroup的子類不用再重載ViewGroup中該方法,因爲它已經有了默認而且標準的view系統流程。dispatchDraw()內部for循環調用drawChild()分別繪製每一個子視圖,而drawChild()內部又會調用draw()函數完成子視圖的內部繪製工作。
- /**
- * {@inheritDoc}
- */
- @Override
- protected void dispatchDraw(Canvas canvas) {
- final int count = mChildrenCount;
- final View[] children = mChildren;
- int flags = mGroupFlags;
- if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
- final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
- final boolean buildCache = !isHardwareAccelerated();
- for (int i = 0; i < count; i++) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
- final LayoutParams params = child.getLayoutParams();
- attachLayoutAnimationParameters(child, params, i, count);
- bindLayoutAnimation(child);
- if (cache) {
- child.setDrawingCacheEnabled(true);
- if (buildCache) {
- child.buildDrawingCache(true);
- }
- }
- }
- }
- final LayoutAnimationController controller = mLayoutAnimationController;
- if (controller.willOverlap()) {
- mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
- }
- controller.start();
- mGroupFlags &= ~FLAG_RUN_ANIMATION;
- mGroupFlags &= ~FLAG_ANIMATION_DONE;
- if (cache) {
- mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;
- }
- if (mAnimationListener != null) {
- mAnimationListener.onAnimationStart(controller.getAnimation());
- }
- }
- int saveCount = 0;
- final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
- if (clipToPadding) {
- saveCount = 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 ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
- for (int i = 0; i < count; i++) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- } else {
- for (int i = 0; i < count; i++) {
- final View child = children[getChildDrawingOrder(count, 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) {
- 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 (debugDraw()) {
- onDebugDraw(canvas);
- }
- if (clipToPadding) {
- canvas.restoreToCount(saveCount);
- }
- // mGroupFlags might have been updated by drawChild()
- flags = mGroupFlags;
- 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() {
- public void run() {
- notifyAnimationListener();
- }
- };
- post(end);
- }
- }
上面基本介紹完了View的繪製流程。更多的細節需要在日常學習中總結。