安卓高手之路之圖形系統(6)requestLayout的流程
當一個View呼叫requestLayout的時候,會給當前的View設定一個FORCE_LAYOUT標記。由此向ViewParent請求佈局。這樣從這個View開始向上一直requestLayout。最終到達ViewRootImpl。ViewParent 就是當前的傳輸鏈。【參見職責鏈設計模式】
第一步。
ViewRootImpl發現請求了佈局。那麼就會呼叫measure方法。
measure方法確認當前View是否有FORCE_LAYOUT標記。
如果有,那麼就會進行重新measure。並且設定標記LAYOUT_REQUIRED。
Java程式碼-
public final
- 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;
- }
第二步。
在隨後的layout方法中,會判斷這個標記。如果這個標記為true。
那麼就一定會呼叫onLayout.
onLayout呼叫後清理LAYOUT_REQUIRED標記。
layout呼叫之後,會清理掉FORCE_LAYOUT標記。
Java程式碼- @SuppressWarnings({"unchecked"})
- 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 & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
- }
- onLayout(changed, l, t, r, b);
- mPrivateFlags &= ~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 &= ~FORCE_LAYOUT;
- }
當然在上述過程中,影響到了兄弟或者是父親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的時候無法對其進行通知,這也是這種實現的缺陷。