【Android - 自定義View】之View的layout過程解析


  layout(布局)的作用是ViewGroup用來確定子元素的位置,在這個過程中會用到兩個核心方法: layout() 和 onLayout() 。layout()方法用來確定View本身的位置,onLayout()方法則用來確定所有子元素的位置。View和ViewGroup中都有layout()和onLayout()兩個方法,但兩個類中都沒有實現onLayout(),其原因和ViewGroup中沒有onMeasure()方法是相同的:因為不同ViewGroup的子類對布局的要求不一樣。

  當我們自定義了一個ViewGroup的時候,會先確定這個ViewGroup的位置,然后,通過重寫 onLayout() 方法,遍歷所有的子元素並調用其 layout() 方法,在layout()方法中onLayout()方法又會被調用。ViewGroup就是通過這個過程,遞歸地對所有子View進行了布局。來看一下View類中的layout()方法的源碼:

/**
 * 本方法用來給一個View和它的所有子View設置尺寸和位置;
 * 這是Android布局機制的第二個階段(第一個階段是測量);
 * 在這個階段中,每個父容器都調用layout()方法來定位它的子View;
 * 子類不能重寫這個方法,而應該重寫onLayout()方法;
 * 在onLayout()方法中調用layout()方法來設置每個子View的位置。
 *
 * @param l 相對於父容器左邊的距離
 * @param t 相對於父容器上邊的距離
 * @param r 相對於父容器右邊的距離
 * @param b 相對於父容器下邊的距離
 */
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);

        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }

        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<View.OnLayoutChangeListener> listenersCopy =
                    (ArrayList<View.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 &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

  從源碼中可以看出這個方法的大致流程:首先通過 setFrame() 方法來設置View的四個位置元素的位置,即初始化mLeft、mTop、mRight和mBottom這四個值。View的四個頂點一旦確定,那么View在父容器中的位置也就確定了;接着會調用 onLayout() 方法,這個方法的用途是父容器確定子元素的位置。

  上面提到,ViewGroup中沒有實現onLayout()方法,原因是不同ViewGroup子類的布局方式不同,因此onLayout()的具體實現需要根據這個ViewGroup子類的布局方式來確定。比如,LinearLayout的onLayout()方法的源碼如下:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        layoutVertical(l, t, r, b);
    } else {
        layoutHorizontal(l, t, r, b);
    }
}

  在自定義布局的時候,我們的任務就是:遍歷所有的子元素,確定它們的大小和位置(大小主要是通過 getMeasuredWidth() 和 getMeasuredHeight() 兩個方法,取出在 onMeasure() 方法中測量得到的寬/高;位置需要自行設置),然后調用 view.layout() 方法或直接調用ViewGroup中的方法 setChildFrame() 方法(setChildFrame()方法內部調用的就是view.layout()方法),將子元素布局到這個ViewGroup中。

  最后還需要說明一點,“測量寬/高” 和 “最終寬/高” 是兩個不同的概念。測量寬/高是在onMeasure()方法中測量得到的寬度或高度,而最終寬/高是在onLayout()方法中最終放置的子元素的寬度或高度。在View的默認實現中,View的測量寬/高和最終寬/高是相等的,但是測量寬/高的賦值時機較早。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM