戲說Android view 工作流程

lostinai發表於2014-05-19

http://blog.csdn.net/aaa2832/article/details/7849400

遍歷View樹performTraversals()執行過程

view樹遍歷概述

還是回到ViewRoot.java,我們直接看performTraversals(),該函式就是android系統View樹遍歷工作的核心。一眼看去,發現這個函式挺長的,但是邏輯是非常清晰的,其執行過程可簡單概括為根據之前所有設定好的狀態,判斷是否需要計算檢視大小(measure)、是否需要重新安置檢視的位置(layout),以及是否需要重繪(draw)檢視,可以用以下圖來表示該流程。

  

  1.   private void performTraversals() {  
  2.         // cache mView since it is used so much below...  
  3. //1 處理mAttachInfo的初始化,並根據resize、visibility改變的情況,給相應的變數賦值。  
  4.         final View host = mView;  
  5.         final View.AttachInfo attachInfo = mAttachInfo;  
  6.         final int viewVisibility = getHostVisibility();  
  7.         boolean viewVisibilityChanged = mViewVisibility != viewVisibility  
  8.                 || mNewSurfaceNeeded;  
  9.         float appScale = mAttachInfo.mApplicationScale;  
  10.         WindowManager.LayoutParams params = null;  
  11.         if (mWindowAttributesChanged) {  
  12.             mWindowAttributesChanged = false;  
  13.             surfaceChanged = true;  
  14.             params = lp;  
  15.         }  
  16.         Rect frame = mWinFrame;  
  17.         if (mFirst) {  
  18.   
  19.             // For the very first time, tell the view hierarchy that it  
  20.             // is attached to the window.  Note that at this point the surface  
  21.             // object is not initialized to its backing store, but soon it  
  22.             // will be (assuming the window is visible).  
  23.             attachInfo.mSurface = mSurface;  
  24.             attachInfo.mUse32BitDrawingCache = PixelFormat.formatHasAlpha(lp.format) ||  
  25.                     lp.format == PixelFormat.RGBX_8888;  
  26.             attachInfo.mHasWindowFocus = false;  
  27.             attachInfo.mWindowVisibility = viewVisibility;  
  28.             ......  
  29.         }   
  30. //2 如果mLayoutRequested判斷為true,那麼說明需要重新layout,不過在此之前那必須重新measure。  
  31.         if (mLayoutRequested) {  
  32.             // Execute enqueued actions on every layout in case a view that was detached  
  33.             // enqueued an action after being detached  
  34.             getRunQueue().executeActions(attachInfo.mHandler);  
  35.   
  36.             if (mFirst) {  
  37.                 ......  
  38.             }   
  39.         }  
  40. //3 判斷是否有子檢視的屬性發生變化,ViewRoot需要獲取這些變化。  
  41.         if (attachInfo.mRecomputeGlobalAttributes) {  
  42.             ......  
  43.         }  
  44.   
  45.         if (mFirst || attachInfo.mViewVisibilityChanged) {  
  46.             ......  
  47.         }  
  48.   
  49.       
  50. //4 根據上面得到的變數數值,確定我們的view需要多大尺寸才能裝下。於是就得measure了,有viewgroup的weight屬性還得再做些處理  
  51.                  // Ask host how big it wants to be  
  52.                 host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  53.                 mLayoutRequested = true;  
  54.             }  
  55.         }  
  56. //5 measure完畢,接下來可以layout了。  
  57.         final boolean didLayout = mLayoutRequested;  
  58.         boolean triggerGlobalLayoutListener = didLayout  
  59.                 || attachInfo.mRecomputeGlobalAttributes;  
  60.         if (didLayout) {  
  61.             host.layout(00, host.mMeasuredWidth, host.mMeasuredHeight);  
  62.   
  63.         }  
  64.   
  65. //6 如果mFirst為true,那麼會進行view獲取焦點的動作。  
  66.         if (mFirst) {  
  67.             mRealFocusedView = mView.findFocus();  
  68.         }  
  69.   
  70.         boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();  
  71. //7 終於,來到最後一步,前面的工作可以說都是鋪墊,都是為了draw而準備的。  
  72.         if (!cancelDraw && !newSurface) {  
  73.             mFullRedrawNeeded = false;  
  74.             draw(fullRedrawNeeded);  
  75. }  

下面我們就來會會view那三部曲吧。

計算檢視大小(measure)的過程

整個view檢視的Measure過程就是一個量體裁衣,按需分配的過程。看一下以下的遞迴過程:


從上圖可以看出,measure過程始於ViewRoot的host.measure(),調的就是view類的measure()函式,該函式然後回撥onMeasure。如果host物件是一個ViewGroup例項,一般會過載onMeasure,如果沒有的話,則會執行view類中預設的onMeasure。合理的情況是程式設計人員過載onMeasure並逐一對裡面的子view進行measure。我們可以看一下view的measure方法:

   

  1. /** 
  2.    * 該方法在需要確定view所需尺寸大小時呼叫,父檢視會提供寬和高的屬性約束。 
  3.    * 具體檢視完全可以在onMeasure中改變這些。 
  4.    * @see #onMeasure(int, int) 
  5.    */  
  6.   public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  7.       if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  8.               widthMeasureSpec != mOldWidthMeasureSpec ||  
  9.               heightMeasureSpec != mOldHeightMeasureSpec) {  
  10.   
  11.           // 首先清除dimension的設值  
  12.           mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
  13.           // measure 自己, 並設定dimension  
  14.           onMeasure(widthMeasureSpec, heightMeasureSpec);  
  15.           // 丟擲未設值flag的異常  
  16.           if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
  17.               throw new IllegalStateException("onMeasure() did not set the"  
  18.                       + " measured dimension by calling"  
  19.                       + " setMeasuredDimension()");  
  20.           }  
  21.           mPrivateFlags |= LAYOUT_REQUIRED;  
  22.       }  
  23.       mOldWidthMeasureSpec = widthMeasureSpec;  
  24.       mOldHeightMeasureSpec = heightMeasureSpec;  
  25.   }  

這裡強烈建議去看看viewGroup例項FrameLayout和LinearLayout的onMeasure方法,一定會有所感悟的,尤其是LinerLayout的。這樣對於viewGroup的專用標籤pading和weight也會有新的體會。

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:orientation="vertical"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="100dip"  
  5.     >  
  6. <TextView    
  7.     android:layout_width="fill_parent"   
  8.     android:layout_height="20dip"   
  9.     android:layout_weight="2"  
  10.     android:text="@string/hello"  
  11.     />  
  12.     <ListView  
  13.     android:layout_width="fill_parent"  
  14.     android:layout_height="fill_parent"  
  15.     android:background="#ff00ff00"  
  16.     ></ListView>  
  17. </LinearLayout>  
  18.   
  19. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  20.     android:orientation="vertical"  
  21.     android:layout_width="fill_parent"  
  22.     android:layout_height="100dip"  
  23.     >  
  24. <TextView    
  25.     android:layout_width="fill_parent"   
  26.     android:layout_height="60dip"   
  27.     android:layout_weight="2"  
  28.     android:text="@string/hello"  
  29.     />  
  30.     <ListView  
  31.     android:layout_width="fill_parent"  
  32.     android:layout_height="0dip"  
  33.     android:layout_weight="2"  
  34.     android:background="#ff00ff00"  
  35.     ></ListView>  
  36. </LinearLayout>  

請問以上兩佈局有無不同,能否自行畫出?

 

佈局(layout)過程

執行完measure過程,也就是說各個view的大小尺寸已經登記在案,現在它們要確定的是自己應該置身於何處,也就是擺放在哪裡。好吧,這個就是layout的職責所在,讓父檢視按照子檢視的大小及佈局引數,將子檢視放置在合適的位置上。

同樣需要看下以下流程圖:


  1.  public void layout(int l, int t, int r, int b) {  
  2.         int oldL = mLeft;  
  3.         int oldT = mTop;  
  4.         int oldB = mBottom;  
  5.         int oldR = mRight;  
  6. //呼叫setFrame()函式給當前檢視設定引數中指定的位置  
  7.         boolean changed = setFrame(l, t, r, b);  
  8.         if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {  
  9.             if (ViewDebug.TRACE_HIERARCHY) {  
  10.                 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);  
  11.             }  
  12. //回撥onLayout()函式  
  13.             onLayout(changed, l, t, r, b);  
  14.             mPrivateFlags &= ~LAYOUT_REQUIRED;  
  15. //4.0新增監聽可捕獲layout變化  
  16.             if (mOnLayoutChangeListeners != null) {  
  17.                 ArrayList<OnLayoutChangeListener> listenersCopy =  
  18.                         (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();  
  19.                 int numListeners = listenersCopy.size();  
  20.                 for (int i = 0; i < numListeners; ++i) {  
  21.                     listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);  
  22.                 }  
  23.             }  
  24.         }  
  25. //layout完成,清楚標籤  
  26.         mPrivateFlags &= ~FORCE_LAYOUT;  
  27.     }  

       view中的該layout函式流程大概如下:

1,呼叫setFrame()將位置引數儲存,這些引數會儲存到view內部變數(mLeft,mTop,mRight,mButtom)。如果有數值的變化那麼會呼叫invalidate()重繪那些區域。

2,回撥onLayout(),View中定義的onLayout()函式預設什麼都不做,View系統提供onLayout函式的目的是為了使系統包含有子檢視的父檢視能夠在onLayout()函式對子檢視進行位置分配,正因為這樣,如果是viewGroup型別,就必須過載onLayout(),由此ViewGroup的onLayout為abstract也就很好解釋了。

3,清楚mPrivateFlags中的LAYOUT_REQUIRED標記,因為layout的操作已經完成了。

為了對layout過程有更深的體會,有必要針對特定的一種viewGroup進行分析,我們還是把魔爪伸向linearLayout,看看它的onMeasure,onMeasure中會根據mOrientation變數,進行不同的layout,我們看一種就行了:

   

  1.  void layoutVertical() {  
  2.         final int paddingLeft = mPaddingLeft;  
  3.         int childTop;  
  4.         int childLeft;         
  5.         // 獲得子檢視可以用的寬度,順便也把子檢視左邊沿的位置計算出來。  
  6.         final int width = mRight - mLeft;  
  7.         int childRight = width - mPaddingRight;       
  8.         // Space available for child  
  9.         int childSpace = width - paddingLeft - mPaddingRight;  
  10.           
  11.         final int count = getVirtualChildCount();  
  12. //根據父檢視中的gravity屬性,決定子檢視的起始位置。  
  13.         final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;  
  14.         final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;  
  15.         switch (majorGravity) {  
  16.            case Gravity.BOTTOM:  
  17.                // mTotalLength contains the padding already  
  18.                childTop = mPaddingTop + mBottom - mTop - mTotalLength;  
  19.                break;  
  20.                // mTotalLength contains the padding already  
  21.            case Gravity.CENTER_VERTICAL:  
  22.                childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;  
  23.                break;  
  24.            case Gravity.TOP:  
  25.            default:  
  26.                childTop = mPaddingTop;  
  27.                break;  
  28.         }  
  29. //遍歷所有子檢視,為它們分配位置.setChildFrame()結果還是會呼叫child.layout()為子檢視設定佈局位置.  
  30.         for (int i = 0; i < count; i++) {  
  31.             final View child = getVirtualChildAt(i);  
  32.             if (child == null) {  
  33.                 childTop += measureNullChild(i);  
  34.     ......  
  35.                 childTop += lp.topMargin;  
  36.                 setChildFrame(child, childLeft, childTop + getLocationOffset(child),  
  37.                         childWidth, childHeight);  
  38.                 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);  
  39.                 i += getChildrenSkipCount(child, i);  
  40.             }  
  41.         }  
  42.     }  

繪製(draw)過程

這是見證奇蹟的時刻,draw過程就是要把view物件繪製到螢幕上,如果它是一個viewGroup,則需要遞迴繪製它所有的子檢視。檢視中有哪些元素是需要繪製的呢?

1,view背景。所有view都會有一個背景,可以是一個顏色值,也可以是一張背景圖片

2,檢視本身內容。比如TextView的文字

3,漸變邊框。就是一個shader物件,讓檢視看起來更具有層次感

4,滾動條。

按照慣例,還是先看一下繪製的總體流程吧:


眼尖的同學應該發現了,上面這張圖比前面的measure和layout多了一步:draw()。在performTraversals()函式中呼叫的是viewRoot的draw()函式,在該函式中進行一系列的前端處理後,再呼叫host.draw()。

一般情況下,View物件不應該過載draw()函式,因此,host.draw()就是view.draw()。該函式內部過程也就是View系統繪製過程的核心過程,該函式中會依次繪製前面所說哦四種元素,其中繪製檢視本身的具體實現就是回撥onDraw()函式,應用程式一般也會過載onDraw()函式以繪製所設計的View的真正介面內容。

Google原始碼的註釋太nice了,我加任何說辭都顯得多餘,就不畫蛇添足了:

  1. public void draw(Canvas canvas) {  
  2.         if (ViewDebug.TRACE_HIERARCHY) {  
  3.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);  
  4.         }  
  5.   
  6.         final int privateFlags = mPrivateFlags;  
  7.         final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&  
  8.                 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);  
  9.         mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;  
  10.   
  11.         /* 
  12.          * Draw traversal performs several drawing steps which must be executed 
  13.          * in the appropriate order: 
  14.          * 
  15.          *      1. Draw the background 
  16.          *      2. If necessary, save the canvas' layers to prepare for fading 
  17.          *      3. Draw view's content 
  18.          *      4. Draw children 
  19.          *      5. If necessary, draw the fading edges and restore layers 
  20.          *      6. Draw decorations (scrollbars for instance) 
  21.          */  
  22.   
  23.         // Step 1, draw the background, if needed  
  24.         int saveCount;  
  25.   
  26.         if (!dirtyOpaque) {  
  27.             final Drawable background = mBGDrawable;  
  28.             if (background != null) {  
  29.                 final int scrollX = mScrollX;  
  30.                 final int scrollY = mScrollY;  
  31.   
  32.                 if (mBackgroundSizeChanged) {  
  33.                     background.setBounds(00,  mRight - mLeft, mBottom - mTop);  
  34.                     mBackgroundSizeChanged = false;  
  35.                 }  
  36.   
  37.                 if ((scrollX | scrollY) == 0) {  
  38.                     background.draw(canvas);  
  39.                 } else {  
  40.                     canvas.translate(scrollX, scrollY);  
  41.                     background.draw(canvas);  
  42.                     canvas.translate(-scrollX, -scrollY);  
  43.                 }  
  44.             }  
  45.         }  
  46.   
  47.         // skip step 2 & 5 if possible (common case)  
  48.         final int viewFlags = mViewFlags;  
  49.         boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;  
  50.         boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;  
  51.         if (!verticalEdges && !horizontalEdges) {  
  52.             // Step 3, draw the content  
  53.             if (!dirtyOpaque) onDraw(canvas);  
  54.   
  55.             // Step 4, draw the children  
  56.             dispatchDraw(canvas);  
  57.   
  58.             // Step 6, draw decorations (scrollbars)  
  59.             onDrawScrollBars(canvas);  
  60.   
  61.             // we're done...  
  62.             return;  
  63.         }  
  64.   
  65.         /* 
  66.          * Here we do the full fledged routine... 
  67.          * (this is an uncommon case where speed matters less, 
  68.          * this is why we repeat some of the tests that have been 
  69.          * done above) 
  70.          */  
  71.   
  72.         boolean drawTop = false;  
  73.         boolean drawBottom = false;  
  74.         boolean drawLeft = false;  
  75.         boolean drawRight = false;  
  76.   
  77.         float topFadeStrength = 0.0f;  
  78.         float bottomFadeStrength = 0.0f;  
  79.         float leftFadeStrength = 0.0f;  
  80.         float rightFadeStrength = 0.0f;  
  81.   
  82.         // Step 2, save the canvas' layers  
  83.         int paddingLeft = mPaddingLeft;  
  84.   
  85.         final boolean offsetRequired = isPaddingOffsetRequired();  
  86.         if (offsetRequired) {  
  87.             paddingLeft += getLeftPaddingOffset();  
  88.         }  
  89.   
  90.         int left = mScrollX + paddingLeft;  
  91.         int right = left + mRight - mLeft - mPaddingRight - paddingLeft;  
  92.         int top = mScrollY + getFadeTop(offsetRequired);  
  93.         int bottom = top + getFadeHeight(offsetRequired);  
  94.   
  95.         if (offsetRequired) {  
  96.             right += getRightPaddingOffset();  
  97.             bottom += getBottomPaddingOffset();  
  98.         }  
  99.   
  100.         final ScrollabilityCache scrollabilityCache = mScrollCache;  
  101.         final float fadeHeight = scrollabilityCache.fadingEdgeLength;          
  102.         int length = (int) fadeHeight;  
  103.   
  104.         // clip the fade length if top and bottom fades overlap  
  105.         // overlapping fades produce odd-looking artifacts  
  106.         if (verticalEdges && (top + length > bottom - length)) {  
  107.             length = (bottom - top) / 2;  
  108.         }  
  109.   
  110.         // also clip horizontal fades if necessary  
  111.         if (horizontalEdges && (left + length > right - length)) {  
  112.             length = (right - left) / 2;  
  113.         }  
  114.   
  115.         if (verticalEdges) {  
  116.             topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));  
  117.             drawTop = topFadeStrength * fadeHeight > 1.0f;  
  118.             bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));  
  119.             drawBottom = bottomFadeStrength * fadeHeight > 1.0f;  
  120.         }  
  121.   
  122.         saveCount = canvas.getSaveCount();  
  123.   
  124.         int solidColor = getSolidColor();  
  125.         if (solidColor == 0) {  
  126.             final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;  
  127.   
  128.             if (drawTop) {  
  129.                 canvas.saveLayer(left, top, right, top + length, null, flags);  
  130.             }  
  131.   
  132.             if (drawBottom) {  
  133.                 canvas.saveLayer(left, bottom - length, right, bottom, null, flags);  
  134.             }  
  135.         } else {  
  136.             scrollabilityCache.setFadeColor(solidColor);  
  137.         }  
  138.   
  139.         // Step 3, draw the content  
  140.         if (!dirtyOpaque) onDraw(canvas);  
  141.   
  142.         // Step 4, draw the children  
  143.         dispatchDraw(canvas);  
  144.   
  145.         // Step 5, draw the fade effect and restore layers  
  146.         final Paint p = scrollabilityCache.paint;  
  147.         final Matrix matrix = scrollabilityCache.matrix;  
  148.         final Shader fade = scrollabilityCache.shader;  
  149.   
  150.         if (drawTop) {  
  151.             matrix.setScale(1, fadeHeight * topFadeStrength);  
  152.             matrix.postTranslate(left, top);  
  153.             fade.setLocalMatrix(matrix);  
  154.             canvas.drawRect(left, top, right, top + length, p);  
  155.         }  
  156. 。。。。。  
  157.         canvas.restoreToCount(saveCount);  
  158.         // Step 6, draw decorations (scrollbars)  
  159.         onDrawScrollBars(canvas);  
  160.     }  

繪製完介面內容後,如果該檢視還包含子檢視,則呼叫dispatchDraw()函式,實際上起作用的還是viewGroup的dispatchDraw()函式。需要說明的是應用程式不應該再過載ViewGroup中該方法,因為它已經有了預設而且標準的view系統流程。dispatchDraw()內部for迴圈呼叫drawChild()分別繪製每一個子檢視,而drawChild()內部又會呼叫draw()函式完成子檢視的內部繪製工作。

  1.  protected void dispatchDraw(Canvas canvas) {  
  2.         final int count = mChildrenCount;  
  3.         final View[] children = mChildren;  
  4.         int flags = mGroupFlags;  
  5. //1 判斷mGroupFlags是否設有FLAG_RUN_ANIMATION標識並且不為0.該layout動畫指的是載入或移除子檢視時候呈現的動畫.  
  6.         if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {  
  7.             final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;  
  8.             final boolean buildCache = !isHardwareAccelerated();//硬體加速,4.0加入.  
  9.             for (int i = 0; i < count; i++) {  
  10.                 final View child = children[i];  
  11.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {  
  12.                     final LayoutParams params = child.getLayoutParams();  
  13.                     attachLayoutAnimationParameters(child, params, i, count);  
  14.                     bindLayoutAnimation(child);  
  15.             }  
  16.   
  17.         }  
  18. //2 處理padding屬性,如果該viewGroup有設定.  
  19.         int saveCount = 0;  
  20.         final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;  
  21.         if (clipToPadding) {  
  22.             saveCount = canvas.save();  
  23.             canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,  
  24.                     mScrollX + mRight - mLeft - mPaddingRight,  
  25.                     mScrollY + mBottom - mTop - mPaddingBottom);  
  26.   
  27.         }  
  28. //3 開始繪製子檢視動畫之前先清除flag.  
  29.         // We will draw our child's animation, let's reset the flag  
  30.         mPrivateFlags &= ~DRAW_ANIMATION;  
  31.         mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;  
  32.   
  33.         boolean more = false;  
  34.         final long drawingTime = getDrawingTime();  
  35. //4 使用佛如迴圈,使viewGroup的子檢視逐個呼叫drawChild函式.  
  36.         if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {  
  37.             for (int i = 0; i < count; i++) {  
  38.                 final View child = children[i];  
  39.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  40.                     more |= drawChild(canvas, child, drawingTime);  
  41.                 }  
  42.             }  
  43.         } else {  
  44.             for (int i = 0; i < count; i++) {  
  45.                 final View child = children[getChildDrawingOrder(count, i)];  
  46.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  47.                     more |= drawChild(canvas, child, drawingTime);  
  48.                 }  
  49.             }  
  50.         }  
  51.   
  52. //5 Draw any disappearing views that have animations  
  53.         if (mDisappearingChildren != null) {  
  54.             final ArrayList<View> disappearingChildren = mDisappearingChildren;  
  55.             final int disappearingCount = disappearingChildren.size() - 1;  
  56.             // Go backwards -- we may delete as animations finish  
  57.             for (int i = disappearingCount; i >= 0; i--) {  
  58.                 final View child = disappearingChildren.get(i);  
  59.                 more |= drawChild(canvas, child, drawingTime);  
  60.             }  
  61.         }  
  62.   
  63.         if (clipToPadding) {  
  64.             canvas.restoreToCount(saveCount);  
  65.         }  
  66.   
  67. // mGroupFlags might have been updated by drawChild()  
  68.         flags = mGroupFlags;  
  69.   
  70.         if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {  
  71.             invalidate(true);  
  72.         }  
  73.     }  

整個view的工作流程那是相當複雜的,這需要靜下心來,加以時日細細揣摩才能略有體會,本人寫有小Demo一個,就當拋磚引玉:ClickMe



相關文章