android ViewGroup layout過程詳解

相比較onMeasure ,layout過程要簡單多了,正如layout的中文意思“佈局”中表達的一樣,layout的過程就是確定View在屏幕上顯示的具體位置,在代碼中就是設置其成員變量mLeft,mTop,mRight,mBottom的值,這幾個值構成的矩形區域就是該View顯示的位置,不過這裏的具體位置都是相對與父視圖的位置。

與onMeasure過程類似,ViewGroup在onLayout函數中通過調用其children的layout函數來設置子視圖相對與父視圖中的位置,具體位置由函數layout的參數決定,當我們繼承ViewGroup時必須重載onLayout函數(ViewGroup中onLayout是abstract修飾),然而onMeasure並不要求必須重載,因爲相對與layout來說,measure過程並不是必須的,具體後面會提到。

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;
    }

函數layout的主體過程還是很容易理解的,首先通過調用setFrame函數來對4個成員變量(mLeft,mTop,mRight,mBottom)賦值,然後回調onLayout函數,最後回調所有註冊過的listener的onLayoutChange函數。
對於View來說,onLayout只是一個空實現,一般情況下我們也不需要重載該函數:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

  }

接着我們來看下ViewGroup.java中layout的源碼:

public final void layout(int l, int t, int r, int b) {
        if (mTransition == null || !mTransition.isChangingLayout()) {
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutSuppressed = true;
        }
}
super.layout(l, t, r, b)調用的即是View.java中的layout函數,相比之下ViewGroup增加了LayoutTransition的處理,LayoutTransition是用於處理ViewGroup增加和刪除子視圖的動畫效果,也就是說如果當前ViewGroup未添加LayoutTransition動畫,或者LayoutTransition動畫此刻並未運行,那麼調用super.layout(l, t, r, b),繼而調用到ViewGroup中的onLayout,否則將mLayoutSuppressed設置爲true,等待動畫完成時再調用requestLayout()。
上面super.layout(l, t, r, b)會調用到ViewGroup.java中onLayout,其源碼實現如下:

@Override
    protected abstract void onLayout(boolean changed,int l, int t, int r, int b){
}
和前面View.java中的onLayout實現相比,唯一的差別就是ViewGroup中多了關鍵字abstract的修飾,也就是說ViewGroup類只能用來被繼承,無法實例化,並且其子類必須重載onLayout函數,而重載onLayout的目的就是安排其children在父視圖的具體位置。重載onLayout通常做法就是起一個for循環調用每一個子視圖的layout(l, t, r, b)函數,傳入不同的參數l, t, r, b來確定每個子視圖在父視圖中的顯示位置。
那layout(l, t, r, b)中的4個參數l, t, r, b如何來確定呢?聯想到之前的measure過程,measure過程的最終結果就是確定了每個視圖的mMeasuredWidth和mMeasuredHeight,這兩個參數可以簡單理解爲視圖期望在屏幕上顯示的寬和高,而這兩個參數爲layout過程提供了一個很重要的依據(但不是必須的),爲了說明這個過程,我們來看下LinearLayout的layout過程:

void layoutVertical() {
        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();

                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }
private void setChildFrame(View child, int left, int top, int width, int height) {        
        child.layout(left, top, left + width, top + height);
}
從setChildFrame可以看到LinearLayout中的子視圖的右邊界等於left + width,下邊界等於top+height,也就是說在LinearLayout中其子視圖顯示的寬和高由measure過程來決定的,因此measure過程的意義就是爲layout過程提供視圖顯示範圍的參考值。
layout過程必須要依靠measure計算出來的mMeasuredWidth和mMeasuredHeight來決定視圖的顯示大小嗎?事實並非如此,layout過程中的4個參數l, t, r, b完全可以由視圖設計者任意指定,而最終視圖的佈局位置和大小完全由這4個參數決定,measure過程得到的mMeasuredWidth和mMeasuredHeight提供了視圖大小的值,但我們完全可以不使用這兩個值,可見measure過程並不是必須的。
說到這裏就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()這兩對函數之間的區別,getMeasuredWidth()、getMeasuredHeight()返回的是measure過程得到的mMeasuredWidth和mMeasuredHeight的值,而getWidth()和getHeight()返回的是mRight - mLeft和mBottom - mTop的值,看View.java中的源碼便一清二楚了:
public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }
public final int getWidth() {
        return mRight - mLeft;
    }

總結:整個layout過程比較容易理解,一般情況下layout過程會參考measure過程中計算得到的mMeasuredWidth和mMeasuredHeight來安排子視圖在父視圖中顯示的位置,但這不是必須的,measure過程得到的結果可能完全沒有實際用處,特別是對於一些自定義的ViewGroup,其子視圖的個數、位置和大小都是固定的,這時候我們可以忽略整個measure過程,只在layout函數中傳入的4個參數來安排每個子視圖的具體位置。
這也解釋了爲什麼有些情況下getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()會得到不同的值。

例子代碼 - 縱向顯示兩個TextView

(1)編寫CustomViewGroup
1. CustomViewGroup繼承自ViewGroup,ViewGroup是所有Layout的父類

public class CustomViewGroup extends ViewGroup {  

}
  1. 覆寫View.onMeasure回調函數,用於計算所有child view的寬高,這裏偷懶沒有進行MeasureSpec模式判斷
@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  

    int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);  

    measureChildren(widthMeasureSpec, heightMeasureSpec);     

    setMeasuredDimension(widthSize, heightSize);  
}
  1. 覆寫ViewGroup.onLayout回調函數,用於指定所有child View的位置
@Override  
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  

    int mTotalHeight = 0;  

       // 遍歷所有子視圖     
    int childCount = getChildCount();  
    for (int i = 0; i < childCount; i++) {  
        View childView = getChildAt(i);  

        // 獲取在onMeasure中計算的視圖尺寸  
        int measureHeight = childView.getMeasuredHeight();  
        int measuredWidth = childView.getMeasuredWidth();  

        childView.layout(left, mTotalHeight, measuredWidth, mTotalHeight + measureHeight);  

        mTotalHeight += measureHeight;  

        Log.e(TAG, "changed = " + changed   
                + ", left = " + left + ", top = " + top   
                + ", right = " + right + ", bottom = " + bottom   
                + ", measureWidth = " + measuredWidth + ", measureHieght = " + measureHeight);  

    }  
}

原文作者: namehybin

原文地址: http://my.eoe.cn/530696/archive/10359.html






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章