您现在的位置:首页 > 博客 > Android开发 > 正文
Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析
http://www.drovik.com/      2013-2-1 15:27:51      来源:老罗的Android之旅      点击:
    接下来,我们就继续分析FrameLayout的成员函数draw的实现,以便可以了解应用程序窗口UI的绘制过程。

       Step 7. FrameLayout.draw

  1. public class FrameLayout extends ViewGroup {  
  2.     ......  
  3.     
  4.     private Drawable mForeground;  
  5.     ......  
  6.   
  7.     public void draw(Canvas canvas) {  
  8.         super.draw(canvas);  
  9.   
  10.         if (mForeground != null) {  
  11.             final Drawable foreground = mForeground;  
  12.   
  13.             if (mForegroundBoundsChanged) {  
  14.                 mForegroundBoundsChanged = false;  
  15.                 final Rect selfBounds = mSelfBounds;  
  16.                 final Rect overlayBounds = mOverlayBounds;  
  17.   
  18.                 final int w = mRight-mLeft;  
  19.                 final int h = mBottom-mTop;  
  20.   
  21.                 if (mForegroundInPadding) {  
  22.                     selfBounds.set(00, w, h);  
  23.                 } else {  
  24.                     selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);  
  25.                 }  
  26.   
  27.                 Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),  
  28.                         foreground.getIntrinsicHeight(), selfBounds, overlayBounds);  
  29.                 foreground.setBounds(overlayBounds);  
  30.             }  
  31.   
  32.             foreground.draw(canvas);  
  33.         }  
  34.     }  
  35.   
  36.     ......  
  37. }  
public class FrameLayout extends ViewGroup { ...... private Drawable mForeground; ...... public void draw(Canvas canvas) { super.draw(canvas); if (mForeground != null) { final Drawable foreground = mForeground; if (mForegroundBoundsChanged) { mForegroundBoundsChanged = false; final Rect selfBounds = mSelfBounds; final Rect overlayBounds = mOverlayBounds; final int w = mRight-mLeft; final int h = mBottom-mTop; if (mForegroundInPadding) { selfBounds.set(0, 0, w, h); } else { selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom); } Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds); foreground.setBounds(overlayBounds); } foreground.draw(canvas); } } ...... }

        这个函数定义在文件frameworks/base/core/java/android/widget/FrameLayout.java中。

        FrameLayout类的成员函数draw首先调用父类View的成员函数draw来绘制它的UI内容,然后再检查它是否设置了一个前景图,即成员变量mForeground的值是否等于null。如果不等于null的话,那么就会先设置这个前景图的大小和位置,然后再调用用成员变量mForeground所指向的一个Drawable对象的成员函数draw来在画布canvas上绘制这个前景图。

        接下来,我们就继续分析View类的成员函数draw的实现,以便可以了解应用程序窗口UI的绘制过程。

        Step 8. View.draw

        这个函数定义在文件frameworks/base/core/java/android/view/View.java中,它主要是完成以下六个操作:

        1. 绘制当前视图的背景。

        2. 保存当前画布的堆栈状态,并且在在当前画布上创建额外的图层,以便接下来可以用来绘制当前视图在滑动时的边框渐变效果。

        3. 绘制当前视图的内容。

        4. 绘制当前视图的子视图的内容。

        5. 绘制当前视图在滑动时的边框渐变效果。

        6. 绘制当前视图的滚动条。

        在上面六个操作中,有些是可以优化的。例如,如果当前视图的某一个子视图是不透明的,并且覆盖了当前视图的内容,那么当前视图的背景以及内容就不会绘制了,即不用执行第1和第3个操作。又如,如果当前视图不是处于滑动的状态,那么第2和第5个操作也是不用执行的。

        接下来我们就分段来阅读View类的成员函数draw的代码:

  1. public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {  
  2.     ......  
  3.   
  4.     public void draw(Canvas canvas) {  
  5.         ......  
  6.   
  7.         final int privateFlags = mPrivateFlags;  
  8.         final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&  
  9.                 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);  
  10.         mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;  
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... public void draw(Canvas canvas) { ...... final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;        这段代码检查View类的成员变量mPrivateFlags的DIRTY_OPAQUE位是否等于1。如果等于1的话,那么就说明当前视图的某一个子视图请求了一个不透明UI绘制操作。在这种情况下,当前视图会被子视图覆盖,因此,就不需要执行前面所说的第1和第3个操作了。不过,不用执行第1和第3个操作还有一个前提,那就是View类的成员变量mAttachInfo所指向的一个AttachInfo对象的成员变量mIgnoreDirtyState的值等于false,这表示当前视图不可以忽略成员变量mPrivateFlags的DIRTY_OPAQUE位。满足了上述两个条件之后,变量dirtyOpaque的值就会等于true。

         View类的成员函数在继续往下执行之前,还会将成员变量mPrivateFlags的DIRTY_MASK位重置为0,以及将DRAWN位设置为1,因为接下来就要开始绘制当前视图的UI了。

         我们继续往下阅读代码:

  1. // Step 1, draw the background, if needed  
  2. int saveCount;  
  3.   
  4. if (!dirtyOpaque) {  
  5.     final Drawable background = mBGDrawable;  
  6.     if (background != null) {  
  7.         final int scrollX = mScrollX;  
  8.         final int scrollY = mScrollY;  
  9.   
  10.         if (mBackgroundSizeChanged) {  
  11.             background.setBounds(00,  mRight - mLeft, mBottom - mTop);  
  12.             mBackgroundSizeChanged = false;  
  13.         }  
  14.   
  15.         if ((scrollX | scrollY) == 0) {  
  16.             background.draw(canvas);  
  17.         } else {  
  18.             canvas.translate(scrollX, scrollY);  
  19.             background.draw(canvas);  
  20.             canvas.translate(-scrollX, -scrollY);  
  21.         }  
  22.     }  
  23. }  
// Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { final Drawable background = mBGDrawable; 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); } } }        这段代码用来执行上述的第1个操作,但是它只会变量dirtyOpaque的值等于false的情况下才会执行。当前视图的背景是通过成员变量mBGDrawable所指向的一个Drawable对象来描述的。在绘制当前视图的背景之前,还会先设置它的大小和位置。

        我们继续往下阅读代码:

  1. // skip step 2 & 5 if possible (common case)  
  2. final int viewFlags = mViewFlags;  
  3. boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;  
  4. boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;  
  5. if (!verticalEdges && !horizontalEdges) {  
  6.     // Step 3, draw the content  
  7.     if (!dirtyOpaque) onDraw(canvas);  
  8.   
  9.     // Step 4, draw the children  
  10.     dispatchDraw(canvas);  
  11.   
  12.     // Step 6, draw decorations (scrollbars)  
  13.     onDrawScrollBars(canvas);  
  14.   
  15.     // we're done...  
  16.     return;  
  17. }  
// 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; }

       这段代码检查是否可以跳过上述的第2和第5个操作。当View类的成员变量mViewFlags的FADING_EDGE_HORIZONTAL位等于1的时候,就说明当前视图正在处于水平滑动状态,并且需要绘制水平边框的渐变效果。同样,当View类的成员变量mViewFlags的FADING_EDGE_VERTICAL位等于1的时候,就说明当前视图正在处于垂直滑动状态,并且需要绘制垂直边框的渐变效果。但是,如果当前视图不是处于滑动状态,即变量horizontalEdges和verticalEdges的值均等于false的时候,那么就不需要执行上述的第2和第5个操作了,而只需要执行第3、第4和第6个操作。注意,当变量dirtyOpaque的值等于true的时候,第3个操作也是不需要执行的。

        我们继续往下分析代码:

  1. boolean drawTop = false;  
  2. boolean drawBottom = false;  
  3. boolean drawLeft = false;  
  4. boolean drawRight = false;  
  5.   
  6. float topFadeStrength = 0.0f;  
  7. float bottomFadeStrength = 0.0f;  
  8. float leftFadeStrength = 0.0f;  
  9. float rightFadeStrength = 0.0f;  
  10.   
  11. // Step 2, save the canvas' layers  
  12. int paddingLeft = mPaddingLeft;  
  13. int paddingTop = mPaddingTop;  
  14.   
  15. final boolean offsetRequired = isPaddingOffsetRequired();  
  16. if (offsetRequired) {  
  17.     paddingLeft += getLeftPaddingOffset();  
  18.     paddingTop += getTopPaddingOffset();  
  19. }  
  20.   
  21. int left = mScrollX + paddingLeft;  
  22. int right = left + mRight - mLeft - mPaddingRight - paddingLeft;  
  23. int top = mScrollY + paddingTop;  
  24. int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop;  
  25.   
  26. if (offsetRequired) {  
  27.     right += getRightPaddingOffset();  
  28.     bottom += getBottomPaddingOffset();  
  29. }  
  30.   
  31. final ScrollabilityCache scrollabilityCache = mScrollCache;  
  32. int length = scrollabilityCache.fadingEdgeLength;  
  33.   
  34. // clip the fade length if top and bottom fades overlap  
  35. // overlapping fades produce odd-looking artifacts  
  36. if (verticalEdges && (top + length > bottom - length)) {  
  37.     length = (bottom - top) / 2;  
  38. }  
  39.   
  40. // also clip horizontal fades if necessary  
  41. if (horizontalEdges && (left + length > right - length)) {  
  42.     length = (right - left) / 2;  
  43. }  
  44.   
  45. if (verticalEdges) {  
  46.     topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));  
  47.     drawTop = topFadeStrength >= 0.0f;  
  48.     bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));  
  49.     drawBottom = bottomFadeStrength >= 0.0f;  
  50. }  
  51.   
  52. if (horizontalEdges) {  
  53.     leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));  
  54.     drawLeft = leftFadeStrength >= 0.0f;  
  55.     rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));  
  56.     drawRight = rightFadeStrength >= 0.0f;  
  57. }  
  58.   
  59. saveCount = canvas.getSaveCount();  
  60.   
  61. int solidColor = getSolidColor();  
  62. if (solidColor == 0) {  
  63.     final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;  
  64.   
  65.     if (drawTop) {  
  66.         canvas.saveLayer(left, top, right, top + length, null, flags);  
  67.     }  
  68.   
  69.     if (drawBottom) {  
  70.         canvas.saveLayer(left, bottom - length, right, bottom, null, flags);  
  71.     }  
  72.   
  73.     if (drawLeft) {  
  74.         canvas.saveLayer(left, top, left + length, bottom, null, flags);  
  75.     }  
  76.   
  77.     if (drawRight) {  
  78.         canvas.saveLayer(right - length, top, right, bottom, null, flags);  
  79.     }  
  80. else {  
  81.     scrollabilityCache.setFadeColor(solidColor);  
  82. }  
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; int paddingTop = mPaddingTop; final boolean offsetRequired = isPaddingOffsetRequired(); if (offsetRequired) { paddingLeft += getLeftPaddingOffset(); paddingTop += getTopPaddingOffset(); } int left = mScrollX + paddingLeft; int right = left + mRight - mLeft - mPaddingRight - paddingLeft; int top = mScrollY + paddingTop; int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop; if (offsetRequired) { right += getRightPaddingOffset(); bottom += getBottomPaddingOffset(); } final ScrollabilityCache scrollabilityCache = mScrollCache; int length = scrollabilityCache.fadingEdgeLength; // 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 >= 0.0f; bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength())); drawBottom = bottomFadeStrength >= 0.0f; } if (horizontalEdges) { leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength())); drawLeft = leftFadeStrength >= 0.0f; rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength())); drawRight = rightFadeStrength >= 0.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); }        这段代码用来检查是否需要保存参数canvas所描述的一块画布的堆栈状态,并且创建额外的图层来绘制当前视图在滑动时的边框渐变效果。视图的边框是绘制在内容区域的边界位置上的,而视图的内容区域是需要排除成员变量mPaddingLeft、mPaddingRight、mPaddingTop和mPaddingBottom所描述的视图内边距的。此外,视图的边框有四个,分别位于视图的左、右、上以及下内边界上。因此,这段代码首先需要计算出当前视图的左、右、上以及下内边距的大小,以便得到边框所要绘制的区域。

        本来通过View类的成员变量mPaddingLeft、mPaddingRight、mPaddingTop和mPaddingBottom就可以得到当视图的左、右、上以及下内边距的大小的,但是有时候我们在定制一个视图的时候,可能会需要在视图的内边距上绘制其它额外的东西,这时候就有扩展视图的内边距的需求。如果有扩展视图的内边距的需求,那么就需要重写View类的成员函数isPaddingOffsetRequired,即将它的返回值设置为true,并且重载另外四个成员函数getLeftPaddingOffset、getRightPaddingOffset、getTopPaddingOffset和getBottomPaddingOffset来提供额外的左、右、上以及下内边距。

       这段代码经过计算后,就得到四个值left、right、top和bottom,它们分别表示当前视图可以用来绘制的内容区域,这个区域已经将内置的和扩展的内边距排除之外。

       计算好left、right、top和bottom这四个值之后,就相当于得到左、右、上以及下边框的起始位置了,但是我还需要知道边框的长度,才能确定左、右、上以及下边框所要绘制的区域。

       边框的长度length设置在View类的成员变量mScrollCache所指向的一个ScrollabilityCache对象的成员变量fadingEdgeLength中。但是,这个预先设置的边框长度length不一定适合当前视图使用。这是因为视图的大小是可以随时改变的,一旦发生了改变之后,原先设置的边框长度length可能就会显得过长。具体来说,就是当上下两个边框或者左右两个边框发生重叠时,就说明原先设置的边框长度过长了。在这种情况下,就要将边框长度length修改为当前视图的内容区域的高度和宽度的较小者的一半,以便可以保证上下两个边框或者左右两个边框不会发生重叠。

       左、右、上以及下边框都对应有一个强度值,强度值的取值范围为[0.0, 1.0]。如果一个边框的强度值等于0.0,那么它就是不可见的,这时候就不需要绘制它的渐变效果。另一方面,如果一个边框的强度值等于1.0,那么它的长度等于原来设置的长度。我们可以将这个强度值理解为一个缩放因子。左、右、上以及下边框的强度值可以分别通过调用View类的成员函数getLeftFadingEdgeStrength、getRightFadingEdgeStrength、getTopFadingEdgeStrength以及getBottomFadingEdgeStrength来获得。注意,只有在变量verticalEdges的值等于true的时候,这段代码才会计算上下两个边框的强度值topFadeStrength和bottomFadeStrength;同样,只有变量horizontalEdges的值等于true的时候,这代码才会计算左右两个边框的强度值leftFadeStrength和rightFadeStrength。 

        计算好左、右、上以及下边框的强度值leftFadeStrength、rightFadeStrength、topFadeStrength以及bottomFadeStrength之后,这段代码就会判断它们的值是否大于0。如果大于0,那么与它们所对应的四个变量drawLeft、drawRight、drawTop以及drawBottom的值就会等于true,表示需要绘制左、右、上以及下四个边框的渐变效果。

        View类的成员函数getSolidColor返回的是当前视图的背景颜色。如果当前视图的背景颜色是纯色的,即变量solidColor的值不等于0,那么这时候就会使用这个背景颜色来绘制边框的渐变效果,即调用变量scrollabilityCache所指向的一个ScrollabilityCache对象的成员函数setFadeColor来将将边框的渐变效果颜色设置为solidColor,这种情况是比较简单的。如果当前视图的背景颜色不是纯色的,即变量solidColor的值等于0,这种情况就比较复杂了,我们需要创建在参数canvas所描述的一块画布上来创建额外的图层来绘制边框的渐变效果,这样做是为了能够使用背景颜色来绘制边框的渐变效果。

        参数canvas所描述的一块画布上来创建额外的图层是通过调用它的成员函数saveLayer来实现的。我们注意到在调用参数canvas所指向的一个Canvas对象的成员函数saveLayer的时候,最后一个参数指定为Canvas.HAS_ALPHA_LAYER_SAVE_FLAG,这表示在将额外创建的图层合成到参数canvas所描述的一块画布上去,要给额外创建的图层设置一个透明度值。同时,我们还可以看出,当前视图的左、右、上和下边框所占据的范围分别为(left, top, left + length, bottom)、(right - length, top, right, bottom)、(left, top, right, top + length)和(left, bottom - length, right, bottom)。还有另外一个地方需要注意的是,在参数canvas所描述的一块画布上来创建额外的图层之前,这段代码首先会获得画布的当前堆栈状态,这是通过一个整数saveCount来描述的,并且这个整数是通过调用参数canvas所指向的一个Canvas对象的成员函数getSaveCount来获得的。这样,后面在额外创建的图层上绘制了边框的渐变效果之后,就可以通过前面得到的整数saveCount将恢复画布的堆栈状态,也就是将前面额外创建的图层合成到画布上来。

        我们接着往下阅读代码:

  1. // Step 3, draw the content  
  2. if (!dirtyOpaque) onDraw(canvas);  
  3.   
  4. // Step 4, draw the children  
  5. dispatchDraw(canvas);  
// Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas);

       这段代码用来执行上述的第3和第4个操作,即绘制当前视图的内容,以及当前视图的子视图的内容,它们分别是通过调用View类的成员函数onDraw和dispatchDraw来实现的。本文主要关注当前视图的子视图的绘制过程,因此,在接下来的Step 9中,我们再详细分析View类的成员函数dispatchDraw的实现。

       我们接着往下阅读代码:

  1.  // Step 5, draw the fade effect and restore layers  
  2.  final Paint p = scrollabilityCache.paint;  
  3.  final Matrix matrix = scrollabilityCache.matrix;  
  4.  final Shader fade = scrollabilityCache.shader;  
  5.  final float fadeHeight = scrollabilityCache.fadingEdgeLength;  
  6.   
  7.  if (drawTop) {  
  8.      matrix.setScale(1, fadeHeight * topFadeStrength);  
  9.      matrix.postTranslate(left, top);  
  10.      fade.setLocalMatrix(matrix);  
  11.      canvas.drawRect(left, top, right, top + length, p);  
  12.  }  
  13.   
  14.  if (drawBottom) {  
  15.      matrix.setScale(1, fadeHeight * bottomFadeStrength);  
  16.      matrix.postRotate(180);  
  17.      matrix.postTranslate(left, bottom);  
  18.      fade.setLocalMatrix(matrix);  
  19.      canvas.drawRect(left, bottom - length, right, bottom, p);  
  20.  }  
  21.   
  22.  if (drawLeft) {  
  23.      matrix.setScale(1, fadeHeight * leftFadeStrength);  
  24.      matrix.postRotate(-90);  
  25.      matrix.postTranslate(left, top);  
  26.      fade.setLocalMatrix(matrix);  
  27.      canvas.drawRect(left, top, left + length, bottom, p);  
  28.  }  
  29.   
  30.  if (drawRight) {  
  31.      matrix.setScale(1, fadeHeight * rightFadeStrength);  
  32.      matrix.postRotate(90);  
  33.      matrix.postTranslate(right, top);  
  34.      fade.setLocalMatrix(matrix);  
  35.      canvas.drawRect(right - length, top, right, bottom, p);  
  36.  }  
  37.   
  38. canvas.restoreToCount(saveCount);  
// Step 5, draw the fade effect and restore layers final Paint p = scrollabilityCache.paint; final Matrix matrix = scrollabilityCache.matrix; final Shader fade = scrollabilityCache.shader; final float fadeHeight = scrollabilityCache.fadingEdgeLength; 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);        这段代码是用来绘制当前视图的左、右、上以及下边框的渐变效果。注意,只有左、右、上以及下边框所对应的四个变量drawLeft、drawRight、drawTop以及drawBottom的值等于true时,左、右、上以及下边框的渐变效果才需要绘制。同时,左、右、上以及下边框在绘制的时候,都会被设置一个缩放因子,即前面计算得到的左、右、上以及下边框的强度值leftFadeStrength、rightFadeStrength、topFadeStrength以及bottomFadeStrength。

        由于当前视图的左、右、上以及下边框的渐变效果是在参数canvas所描述的一块画布的额外创建的图层上绘制的,因此,在绘制完之后,这段代码需要调用参数canvas所指向的一个Canvas对象来恢复参数canvas所描述的一块画布在创建额外图层时的堆栈状态,即相当于是将前面所绘制的边框渐变效果合成到参数canvas所描述的一块画布来。

        我们继续往下阅读最后一段代码:

  1.         // Step 6, draw decorations (scrollbars)  
  2.         onDrawScrollBars(canvas);  
  3.     }  
  4.   
  5.     ......  
  6. }  
// Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); } ...... }       这段代码用来执行上述的第6个操作,即调用View类的成员函数onDrawScrollBars来绘制当前视图的滚动条。

       接下来,我们就主要关注当前视图的子视图的绘制过程,即View类的成员函数dispatchDraw的实现。注意,View类的成员函数dispatchDraw是一个空实现,它是由子类ViewGroup来重写的,也就是说,只有当一个视图描述的是一个视图容器时,它才会重写父类View的成员函数dispatchDraw。

       前面我们已经假设当前正在处理的视图是应用程序窗口的顶层视图,即它是一个类型为DecorView视图。DecorView类是从ViewGroup类继承下来的,并且在ViewGroup类中重写了父类View类的成员函数dispatchDraw。因此,接下来我们就继续分析ViewGroup的成员函数dispatchDraw的实现。

       Step 9. ViewGroup.dispatchDraw

       这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中,它的实现比较长,我们分段来阅读:

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {  
  2.     ......  
  3.   
  4.     @Override  
  5.     protected void dispatchDraw(Canvas canvas) {  
  6.         final int count = mChildrenCount;  
  7.         final View[] children = mChildren;  
  8.         int flags = mGroupFlags;  
public abstract class ViewGroup extends View implements ViewParent, ViewManager { ...... @Override protected void dispatchDraw(Canvas canvas) { final int count = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags;       ViewGroup类的成员变量mChildrenCount描述的是当前视图组的子视图的个数,另外一个成员变量mChildren是一个类型为View的数组,用来保存当前视图组的子视图。此外,ViewGroup类的成员变量mGroupFlags用来描述当前视图组的标志位。这段代码将上述ViewGroup类的三个成员变量分别保存在变量count、children和flags中,以便接下来可以访问。

       我们继续往下阅读代码:

  1. if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {  
  2.     ......  
  3.   
  4.     for (int i = 0; i < count; i++) {  
  5.         final View child = children[i];  
  6.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {  
  7.             ......  
  8.             bindLayoutAnimation(child);  
  9.             ......  
  10.         }  
  11.     }  
  12.   
  13.     final LayoutAnimationController controller = mLayoutAnimationController;  
  14.     ......  
  15.   
  16.     controller.start();  
  17.   
  18.     ......  
  19.   
  20.     if (mAnimationListener != null) {  
  21.         mAnimationListener.onAnimationStart(controller.getAnimation());  
  22.     }  
  23. }  
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { ...... for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { ...... bindLayoutAnimation(child); ...... } } final LayoutAnimationController controller = mLayoutAnimationController; ...... controller.start(); ...... if (mAnimationListener != null) { mAnimationListener.onAnimationStart(controller.getAnimation()); } }

        这段代码用来检查当前视图组的子视图是否需要显示动画。如果变量flags的FLAG_RUN_ANIMATION位等于1,并且ViewGroup类的成员函数canAnimate的返回值等于true,即当前当前视图组允许其子视图显示动画,那么这段代码接下来就要开始显示动画了。

        这段代码首先检查当前视图组的每一个子视图child,如果它是可见的,那么就会调用ViewGroup类的另外一个成员函数bindLayoutAnimation来设置它的动画。设置完成子视图的动画之后,这段代码接下来再调用ViewGroup类的成员变量mLayoutAnimationController所指向的一个LayoutAnimationController对象的成员函数start来启动动画,并且调用ViewGroup类的成员变量mAnimationListener所指向的一个AnimationListener对象的成员函数onAnimationStart来通知那些注册到当前视图组的动画监听者,当前视图组开始显示动画了。

        我们继续往下阅读代码:

  1. int saveCount = 0;  
  2. final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;  
  3. if (clipToPadding) {  
  4.     saveCount = canvas.save();  
  5.     canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,  
  6.             mScrollX + mRight - mLeft - mPaddingRight,  
  7.             mScrollY + mBottom - mTop - mPaddingBottom);  
  8.   
  9. }  
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); }        这段代码检查变量flags的CLIP_TO_PADDING_MASK位是否不等于1。如果不等于1的话,那么就说明需要设置参数canvas所描述的一块画布的剪裁区域,使得这个裁剪区域不包含当前视图组的内边距。注意,当前视图组的内边距是通过从父类View继承下来的四个成员变量mPaddingLeft、mPaddingRight、mPaddingTop和mPaddingBottom来描述的。此外,当前视图组的区域是通过从父类继承下来的四个成员变量量mLeft、mRight、mTop和mBottom描述的。再结合当前视图的当前滚动位置mScrollX的mScrollY,就可以计算出参数canvas所描述的一块画布的剪裁区域。

        在设置参数canvas所描述的一块画布的剪裁区域之前,这段代码会先调用参数canvas所指向的一个Canvas对象的成员函数save来保存它的堆栈状态,以便在绘制完成当前视图组的UI之后,可以恢复canvas所描述的一块画布的堆栈状态。

        我们继续往下阅读代码:

  1. boolean more = false;  
  2. final long drawingTime = getDrawingTime();  
  3.   
  4. if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {  
  5.     for (int i = 0; i < count; i++) {  
  6.         final View child = children[i];  
  7.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  8.             more |= drawChild(canvas, child, drawingTime);  
  9.         }  
  10.     }  
  11. else {  
  12.     for (int i = 0; i < count; i++) {  
  13.         final View child = children[getChildDrawingOrder(count, i)];  
  14.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  15.             more |= drawChild(canvas, child, drawingTime);  
  16.         }  
  17.     }  
  18. }  
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); } } }       这段代码用来绘制当前视图组的子视图。如果一个子视图child是可见的,即它的成员变量mViewFlags的VISIBLE位等于1,或者它有一个动画需要显示,即它的成员函数getAnimation的返回值不等于null,那么这个子视图就是需要绘制的。绘制一个子视图是通过调用ViewGroup类的成员函数drawChild来实现的。ViewGroup类的成员函数drawChild在绘制一个子视图的时候,如果这个子视图的动画还没有结束,那么它的返回值就等于true,并且会被设置到变量more中去。

       注意,当变量flags的FLAG_USE_CHILD_DRAWING_ORDER位等于0的时候,就表示当前视图组的子视图按照它们在数组children中的位置从小到在三类绘制,否则的话,就需要通过ViewGroup类的成员函数getChildDrawingOrder来决定这些子视图的绘制顺序。

       我们接着往下阅读代码:

  1. // Draw any disappearing views that have animations  
  2. if (mDisappearingChildren != null) {  
  3.     final ArrayList disappearingChildren = mDisappearingChildren;  
  4.     final int disappearingCount = disappearingChildren.size() - 1;  
  5.     // Go backwards -- we may delete as animations finish  
  6.     for (int i = disappearingCount; i >= 0; i--) {  
  7.         final View child = disappearingChildren.get(i);  
  8.         more |= drawChild(canvas, child, drawingTime);  
  9.     }  
  10. }  
// Draw any disappearing views that have animations if (mDisappearingChildren != null) { final ArrayList 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); } }       ViewGroup类的成员变量mDisappearingChildren用来保存那些正在消失的子视图,但是这些子视图正在显示动画的过程中,因此,这些子视图也是需要绘制的,这段代码同样是通过调用ViewGroup类的成员函数drawChild来绘制它们。

      我们继续往下阅读最后一段代码:

  1.     if (clipToPadding) {  
  2.         canvas.restoreToCount(saveCount);  
  3.     }  
  4.   
  5.     // mGroupFlags might have been updated by drawChild()  
  6.     flags = mGroupFlags;  
  7.   
  8.     if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {  
  9.         invalidate();  
  10.     }  
  11.   
  12.     if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&  
  13.             mLayoutAnimationController.isDone() && !more) {  
  14.         // We want to erase the drawing cache and notify the listener after the  
  15.         // next frame is drawn because one extra invalidate() is caused by  
  16.         // drawChild() after the animation is over  
  17.         mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;  
  18.         final Runnable end = new Runnable() {  
  19.            public void run() {  
  20.                notifyAnimationListener();  
  21.            }  
  22.         };  
  23.         post(end);  
  24.     }  
  25. }  
  26.   
  27. ......  
if (clipToPadding) { canvas.restoreToCount(saveCount); } // mGroupFlags might have been updated by drawChild() flags = mGroupFlags; if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) { invalidate(); } 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); } } ...... }        这段代码执行以下三个操作:

        1. 检查变量clipToPadding的值是否等于true。如果是的话,那么就说明前面设置过参数canvas所描述的一块画布的裁剪区域。由于现在已经在这块画布上绘制完成当前视图组的UI了,因此,就需要恢复参数canvas所描述的一块画布堆栈状态。这是通过调用参数canvas所指向的一个Canvas对象的成员函数restoreToCount来实现的。

        2. 前面在绘制当前视图组的子视图的UI的时候,有可能会需要修改当前视图组的标志位,即修改ViewGroup类的成员变量mGroupFlags的值。如果修改后的mGroupFlags的FLAG_INVALIDATE_REQUIRED位等于1,那么就说明当前视图组需要重新发起一个绘制UI的请求。这是通过调用ViewGroup类的另外一个成员函数invalidate来实现的。

        3. 如果当前视图组的动画已经显示完成,并且当前视图组的子视图的动画也已经显示完成,再并且当前视图组注册有动画监听者,那么就是会调用ViewGroup类的另外一个成员函数notifyAnimationListener来通知这些动画监听者,当前视图组的动画已经显示结束。注意,ViewGroup类的成员函数notifyAnimationListener是以消息的形式来调用的,即ViewGroup类的成员函数dispatchDraw不是在动画一显示结束,就马上通知那些动画监听者。

        接下来,我们就继续分析ViewGroup类的成员函数drawChild的实现,以便可以了解一个视图组的子视图的绘制过程。

        Step 10. ViewGroup.drawChild

        这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中,它的实现比较长,我们分段来阅读:

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {  
  2.     ......  
  3.   
  4.     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {  
  5.         boolean more = false;  
  6.   
  7.         final int cl = child.mLeft;  
  8.         final int ct = child.mTop;  
  9.         final int cr = child.mRight;  
  10.         final int cb = child.mBottom;  
  11.   
  12.         final int flags = mGroupFlags;  
public abstract class ViewGroup extends View implements ViewParent, ViewManager { ...... protected boolean drawChild(Canvas canvas, View child, long drawingTime) { boolean more = false; final int cl = child.mLeft; final int ct = child.mTop; final int cr = child.mRight; final int cb = child.mBottom; final int flags = mGroupFlags;

       这段代码首先获得子视图child的区域(cl, ct, cr, cb),以及当前视图组的标志位flags,以便接下来可以使用。另外,变量more的值用来表示子视图child是否还在显示动画。

       我们接着往下阅读代码:

  1. Transformation transformToApply = null;  
  2. final Animation a = child.getAnimation();  
  3. ......  
  4.   
  5. if (a != null) {  
  6.     ......  
  7.   
  8.     if (mChildTransformation == null) {  
  9.         mChildTransformation = new Transformation();  
  10.     }  
  11.     more = a.getTransformation(drawingTime, mChildTransformation);  
  12.     transformToApply = mChildTransformation;  
  13.     ......  
  14.   
  15. else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==  
  16.         FLAG_SUPPORT_STATIC_TRANSFORMATIONS) {  
  17.     if (mChildTransformation == null) {  
  18.         mChildTransformation = new Transformation();  
  19.     }  
  20.     final boolean hasTransform = getChildStaticTransformation(child, mChildTransformation);  
  21.     if (hasTransform) {  
  22.         final int transformType = mChildTransformation.getTransformationType();  
  23.         transformToApply = transformType != Transformation.TYPE_IDENTITY ?  
  24.                 mChildTransformation : null;  
  25.         ......  
  26.     }  
  27. }  
Transformation transformToApply = null; final Animation a = child.getAnimation(); ...... if (a != null) { ...... if (mChildTransformation == null) { mChildTransformation = new Transformation(); } more = a.getTransformation(drawingTime, mChildTransformation); transformToApply = mChildTransformation; ...... } else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) == FLAG_SUPPORT_STATIC_TRANSFORMATIONS) { if (mChildTransformation == null) { mChildTransformation = new Transformation(); } final boolean hasTransform = getChildStaticTransformation(child, mChildTransformation); if (hasTransform) { final int transformType = mChildTransformation.getTransformationType(); transformToApply = transformType != Transformation.TYPE_IDENTITY ? mChildTransformation : null; ...... } }        这段代码用来获得子视图child的变换矩阵transformToApply。获得了子视图child的变换矩阵transformToApply之后,我们就可以知道如何来显示它了。 在两种情况下,子视图child会被设置一个变换矩阵。第一种情况子视图child正在显示动画的过程中,第二种情况是当前视图组给每一个子视图设置了一个变换矩阵。下面我们就分别讨论这两种情况。

       对于第一种情况,子视图child的成员函数getAnimation的返回值a不等于null,并且它所指向的一个Animation对象就是用来描述子视图child的动画的。获得了子视图的动画对象a之后,我们就可以调用它的成员函数getTransformation来继续执行它的动画了。如果该动画还需要继续执行,那么调用Animation对象a的成员函数getTransformation的返回值more就会等于true,并且会返回子视图child的接下来需要使用的变换矩阵,保存在ViewGroup类的成员变量mChildTransformation中。ViewGroup类的成员变量mChildTransformation最后又会保存在变量transformToApply中。

       对于第二种情况,变量flags的FLAG_SUPPORT_STATIC_TRANSFORMATIONS位等于1,这时候调用ViewGroup类的成员函数getChildStaticTransformation就可以知道子视图child是否被设置了一个变换矩阵。如果设置了的话,那么ViewGroup类的成员函数getChildStaticTransformation的返回值hasTransform就会等于true。在这种情况下,ViewGroup类的成员变量mChildTransformation所描述的变换矩阵就是要应用在子视图child中的。不过有一个前提,即ViewGroup类的成员变量mChildTransformation所描述的变换矩阵不是一个单位矩阵,这是因为单位矩阵是没有变换效果的。如果ViewGroup类的成员变量mChildTransformation所描述的变换矩阵不是一个单位矩阵,那么它同样会被保存在变量transformToApply中。

 

 未完。。。

分享到:
发表评论(0)
姓名 *
评论内容 *
验证码 *图片看不清?点击重新得到验证码