1. 程式人生 > >安卓高手之路之圖形系統(6)requestLayout的流程

安卓高手之路之圖形系統(6)requestLayout的流程

當一個View呼叫requestLayout的時候,會給當前的View設定一個FORCE_LAYOUT標記。由此向ViewParent請求佈局。這樣從這個View開始向上一直requestLayout。最終到達ViewRootImpl。ViewParent 就是當前的傳輸鏈。【參見職責鏈設計模式】

第一步。

ViewRootImpl發現請求了佈局。那麼就會呼叫measure方法。

measure方法確認當前View是否有FORCE_LAYOUT標記。

如果有,那麼就會進行重新measure。並且設定標記LAYOUT_REQUIRED。

Java程式碼  收藏程式碼
  1. public final
     void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.         if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  3.                 widthMeasureSpec != mOldWidthMeasureSpec ||  
  4.                 heightMeasureSpec != mOldHeightMeasureSpec) {  
  5.             // first clears the measured dimension flag
      
  6.             mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
  7.             if (ViewDebug.TRACE_HIERARCHY) {  
  8.                 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);  
  9.             }  
  10.             // measure ourselves, this should set the measured dimension flag back  
  11.             onMeasure(widthMeasureSpec, heightMeasureSpec);  
  12.             // flag not set, setMeasuredDimension() was not invoked, we raise  
  13.             // an exception to warn the developer  
  14.             if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
  15.                 throw new IllegalStateException("onMeasure() did not set the"  
  16.                         + " measured dimension by calling"  
  17.                         + " setMeasuredDimension()");  
  18.             }  
  19.             mPrivateFlags |= LAYOUT_REQUIRED;  
  20.         }  
  21.         mOldWidthMeasureSpec = widthMeasureSpec;  
  22.         mOldHeightMeasureSpec = heightMeasureSpec;  
  23.     }  

第二步。

 在隨後的layout方法中,會判斷這個標記。如果這個標記為true。

那麼就一定會呼叫onLayout.

onLayout呼叫後清理LAYOUT_REQUIRED標記。

layout呼叫之後,會清理掉FORCE_LAYOUT標記。

Java程式碼  收藏程式碼
  1. @SuppressWarnings({"unchecked"})  
  2.    public void layout(int l, int t, int r, int b) {  
  3.        int oldL = mLeft;  
  4.        int oldT = mTop;  
  5.        int oldB = mBottom;  
  6.        int oldR = mRight;  
  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(changed, l, t, r, b);  
  13.            mPrivateFlags &= ~LAYOUT_REQUIRED;  
  14.            ListenerInfo li = mListenerInfo;  
  15.            if (li != null && li.mOnLayoutChangeListeners != null) {  
  16.                ArrayList<OnLayoutChangeListener> listenersCopy =  
  17.                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();  
  18.                int numListeners = listenersCopy.size();  
  19.                for (int i = 0; i < numListeners; ++i) {  
  20.                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);  
  21.                }  
  22.            }  
  23.        }  
  24.        mPrivateFlags &= ~FORCE_LAYOUT;  
  25.    }  

當然在上述過程中,影響到了兄弟或者是父親View的大小, 那麼也兄弟或者是父親View也會呼叫layout/onLayout。不管其是否已經呼叫requestLayout。如果說指定的MeasureSpec為此也發生了變化,

那麼measure/onMeasure也會被呼叫。

通過上述分析發現,只要呼叫了requestlayout, 那麼measure和onMeasure,以及layout,onlayout,draw onDraw都會被呼叫。

在很多情況下,requestLayout是不需要被呼叫的。例如,我們把一個AbsoluteLayout裡面的childView挪動一下位置。我們僅僅需要呼叫的可能就是重新佈局當前AbsoluteLayout,然後呼叫invalidate方法進行重繪。而不是從當前View向上的整個View樹形結構都要重新layout,onLayout,measure,onMeasure一次。

這個時候,怎麼辦?

一種方法是,直接呼叫onLayout。然後呼叫invalidate進行重繪。很明顯可以提升繪製效率。由於父View的layout實現中對會通知佈局的listener。但是由於無法得到listener,因此呼叫onlayout的時候無法對其進行通知,這也是這種實現的缺陷。