源碼解析Android中View的layout版面配置階段

來源:互聯網
上載者:User

源碼解析Android中View的layout版面配置階段

Android中的Veiw從記憶體中到呈現在UI介面上需要依次經曆三個階段:量算 -> 布局 -> 繪圖,關於View的量算、布局、繪圖的總體機制可參見博文 《 Android中View的布局及繪圖機制》。量算是布局的基礎,如果想瞭解量算的細節,可參見博文《源碼解析Android中View的measure量算過程》。本文將從源碼角度解析View的布局layout過程,本文會詳細介紹View版面配置階段中的關鍵方法,並對源碼加上了注釋以進行說明。

對View進行布局的目的是計算出View的尺寸以及在其父控制項中的位置,具體來說就是計算出View的四條邊界分別到其父控制項左邊界、上邊界的距離,即計算View的left、top、right、bottom的值。

layout

layout()方法是View布局的入口,其源碼如下所示:

    public void layout(int l, int t, int r, int b) {        //成員變數mPrivateFlags3中的一些位元位儲存著和layout相關的資訊        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {            //如果在mPrivateFlags3的低位位元組的第4位(從最右向左數第4位)的值為1,            //那麼就表示在layout布局前需要先對View進行量算,            //這種情況下就會執行View的onMeasure方法對View進行量算            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);            //量算完成後就會將mPrivateFlags3低位位元組的第4位重設為0,            //移除掉標籤PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;        }        int oldL = mLeft;        int oldT = mTop;        int oldB = mBottom;        int oldR = mRight;        //如果isLayoutModeOptical()返回true,那麼就會執行setOpticalFrame()方法,        //否則會執行setFrame()方法。並且setOpticalFrame()內部會調用setFrame(),        //所以無論如何都會執行setFrame()方法。        //setFrame()方法會將View新的left、top、right、bottom儲存到View的成員變數中        //並且返回一個boolean值,如果返回true表示View的位置或尺寸發生了變化,        //否則表示未發生變化        boolean changed = isLayoutModeOptical(mParent) ?                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {            //如果View的布局發生了變化,或者mPrivateFlags有需要LAYOUT的標籤PFLAG_LAYOUT_REQUIRED,            //那麼就會執行以下代碼            //首先會觸發onLayout方法的執行,View中預設的onLayout方法是個空方法            //不過繼承自ViewGroup的類都需要實現onLayout方法,從而在onLayout方法中依次迴圈子View,            //並調用子View的layout方法            onLayout(changed, l, t, r, b);            //在執行完onLayout方法之後,從mPrivateFlags中移除標籤PFLAG_LAYOUT_REQUIRED            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;            //我們可以通過View的addOnLayoutChangeListener(View.OnLayoutChangeListener listener)方法            //向View中添加多個Layout發生變化的事件監聽器            //這些事件監聽器都儲存在mListenerInfo.mOnLayoutChangeListeners這個ArrayList中            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnLayoutChangeListeners != null) {                //首先對mOnLayoutChangeListeners中的事件監聽器進行拷貝                ArrayList listenersCopy =                        (ArrayList)li.mOnLayoutChangeListeners.clone();                int numListeners = listenersCopy.size();                for (int i = 0; i < numListeners; ++i) {                    //遍曆註冊的事件監聽器,依次調用其onLayoutChange方法,這樣Layout事件監聽器就得到了響應                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);                }            }        }        //從mPrivateFlags中移除強制Layout的標籤PFLAG_FORCE_LAYOUT        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;        //向mPrivateFlags3中加入Layout完成的標籤PFLAG3_IS_LAID_OUT        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;    }

在layout()方法內部剛開始執行的時候,首先會根據mPrivateFlags3變數是否具有標誌位PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT判斷是否需要執行View的onMeasure()方法。如果具有標誌位PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT,則執行onMeasure()方法,從而對View進行量算,量算的結果會儲存到View的成員變數中。量算完成後就會將mPrivateFlags3低位位元組的第4位重設為0,移除掉標籤PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT。

如果isLayoutModeOptical()返回true,那麼就會執行setOpticalFrame()方法,否則會執行setFrame()方法。並且setOpticalFrame()內部會調用setFrame(),所以無論如何都會執行setFrame()方法。setFrame()方法會將View新的left、top、right、bottom儲存到View的成員變數中,並且返回一個boolean值,如果返回true表示View的位置或尺寸發生了變化,否則表示未發生變化。後面會對setFrame()方法詳細介紹。

如果View的布局發生了變化,或者mPrivateFlags有需要LAYOUT的標籤PFLAG_LAYOUT_REQUIRED,就會觸發onLayout方法的執行,View中預設的onLayout方法是個空方法。不過繼承自ViewGroup的類都需要實現onLayout方法,從而在onLayout方法中依次迴圈子View,並調用子View的layout方法。在執行完onLayout方法之後,從mPrivateFlags中移除標籤PFLAG_LAYOUT_REQUIRED。然後會遍曆註冊的Layout Change事件監聽器,依次調用其onLayoutChange方法,這樣Layout事件監聽器就得到了響應。

最後,從mPrivateFlags中移除強制Layout的標籤PFLAG_FORCE_LAYOUT,向mPrivateFlags3中加入Layout完成的標籤PFLAG3_IS_LAID_OUT。

setFrame

setFrame()方法是具體用來完成給View分配尺寸以及位置工作的,在layout()方法中會調用setFrame()方法。其源碼如下所示:

    protected boolean setFrame(int left, int top, int right, int bottom) {        boolean changed = false;        if (DBG) {            Log.d("View", this + " View.setFrame(" + left + "," + top + ","                    + right + "," + bottom + ")");        }        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {            //將新舊left、right、top、bottom進行對比,只要不完全相對就說明View的布局發生了變化,            //則將changed變數設定為true            changed = true;            //先儲存一下mPrivateFlags中的PFLAG_DRAWN標籤資訊            int drawn = mPrivateFlags & PFLAG_DRAWN;            //分別計算View的新舊尺寸            int oldWidth = mRight - mLeft;            int oldHeight = mBottom - mTop;            int newWidth = right - left;            int newHeight = bottom - top;            //比較View的新舊尺寸是否相同,如果尺寸發生了變化,那麼sizeChanged的值為true            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);            // Invalidate our old position            invalidate(sizeChanged);            //將新的left、top、right、bottom儲存到View的成員變數中            mLeft = left;            mTop = top;            mRight = right;            mBottom = bottom;            //mRenderNode.setLeftTopRightBottom()方法會調用RenderNode中原生方法的nSetLeftTopRightBottom()方法,            //該方法會根據left、top、right、bottom更新用於渲染的顯示列表            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);            //向mPrivateFlags中增加標籤PFLAG_HAS_BOUNDS,表示當前View具有了明確的邊界範圍            mPrivateFlags |= PFLAG_HAS_BOUNDS;            if (sizeChanged) {                //如果View的尺寸和之前相比發生了變化,那麼就執行sizeChange()方法,                //該方法中又會調用onSizeChanged()方法,並將View的新舊尺寸傳遞進去                sizeChange(newWidth, newHeight, oldWidth, oldHeight);            }            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {                //有可能在調用setFrame方法之前,invalidate方法就被調用了,                //這會導致mPrivateFlags移除了PFLAG_DRAWN標籤。                //如果當前View處於可見狀態就將mPrivateFlags強制添加PFLAG_DRAWN狀態位,                //這樣會確保下面的invalidate()方法會執行到其父控制項層級。                mPrivateFlags |= PFLAG_DRAWN;                invalidate(sizeChanged);                //invalidateParentCaches()方法會移除其父控制項的PFLAG_INVALIDATED標籤,                //這樣其父控制項就會重建用於渲染的顯示列表                invalidateParentCaches();            }            // 重新恢複mPrivateFlags中原有的PFLAG_DRAWN標籤資訊            mPrivateFlags |= drawn;            mBackgroundSizeChanged = true;            if (mForegroundInfo != null) {                mForegroundInfo.mBoundsChanged = true;            }            notifySubtreeAccessibilityStateChangedIfNeeded();        }        return changed;    }

在該方法中,會將新舊left、right、top、bottom進行對比,只要不完全相同就說明View的布局發生了變化,則將changed變數設定為true。然後比較View的新舊尺寸是否相同,如果尺寸發生了變化,並將其儲存到變數sizeChanged中。如果尺寸發生了變化,那麼sizeChanged的值為true。

然後將新的left、top、right、bottom儲存到View的成員變數中儲存下來。並執行mRenderNode.setLeftTopRightBottom()方法會,其會調用RenderNode中原生方法的nSetLeftTopRightBottom()方法,該方法會根據left、top、right、bottom更新用於渲染的顯示列表。

如果View的尺寸和之前相比發生了變化,那麼就執行sizeChange()方法,該方法中又會調用onSizeChanged()方法,並將View的新舊尺寸傳遞進去。

如果View處於可見狀態,那麼會調用invalidate和invalidateParentCaches方法。invalidateParentCaches()方法會移除其父控制項的PFLAG_INVALIDATED標籤,這樣其父控制項就會重建用於渲染的顯示列表。

sizeChange

sizeChange方法會在View的尺寸發生變化時調用,在setFrame()方法中就可能會調用sizeChange()方法。當然,在View的setLeft()、setTop()、setRight()、setBottom()等其他改變View尺寸的方法中也會調用sizeChange()方法,其源碼如下所示:

    private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {        //將View的新舊尺寸傳遞給onSizeChanged()方法        onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);        if (mOverlay != null) {            mOverlay.getOverlayView().setRight(newWidth);            mOverlay.getOverlayView().setBottom(newHeight);        }        rebuildOutline();    }

在該方法中其主要將View的新舊尺寸傳遞給onSizeChanged()方法使其執行。

onSizeChanged

onSizeChanged()方法是個空方法,代碼如下所示:

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {    }

該方法會在View的尺寸發生變化時,通過sizeChange()方法的執行而被調用。當View第一次加入到View樹中時,該方法也會被調用,只不過傳入的舊尺寸oldWidth和oldHeight都是0。

總結

layout方法總的調用過程主線如下所示:

layout() -> onMeasure() -> setFrame() -> sizeChange() -> onSizeChanged() -> onLayout() ->遍曆執行OnLayoutChangeListener.onLayoutChange()

希望本文對大家理解View的layout版面配置階段有所協助!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.