前段開始學(xué)習(xí)View的工作原理,前兩篇博客的草稿都已經(jīng)寫好了,本想一鼓作氣寫完所有的相關(guān)文章,然后經(jīng)歷了一段連續(xù)加班,結(jié)果今天準(zhǔn)備繼續(xù)寫文章時,把之前寫好的東西都忘記了,又重新梳理了一遍,所以說那怕就是已經(jīng)掌握的知識,也要記得溫故而知新。

言歸正傳,之前我們討論過了measure過程,measure過程完成之后,我們就可以通過 getMeasuredWidthgetMeasuredHeight來得到View的寬高尺寸了。而知道了寬高尺寸之后,剩下的就是布局(layout)過程了。說直白點,怎么把具一個寬高已定的View擺放在屏幕上。

我們先來看一個demo。

/**
 *@author www.yaoxiaowen.com
 */<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/linearLayout1"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:background="@color/google_yellow"
    >

    <LinearLayout        android:id="@+id/linearLayout2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:orientation="horizontal"        android:background="@color/white"
        >

        <TextView            android:id="@+id/textView1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="textView1"            android:background="@color/green"            android:paddingLeft="20dp"            android:paddingRight="40dp"            android:paddingTop="10dp"
            />

        <Button            android:id="@+id/button1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="button1"            android:layout_marginLeft="20dp"            android:background="@color/blue"
            />
    </LinearLayout>

    <TextView        android:id="@+id/text2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="text2"        android:layout_marginLeft="40dp"        android:background="@color/red"
        /></LinearLayout>

具體的實現(xiàn)效果,是這個樣子。

萬碼學(xué)堂,電腦培訓(xùn),計算機培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

我們暫且不討論代碼當(dāng)中的實現(xiàn)原理之類的,僅僅就想想一種場景,給你一些尺寸固定但不同的盒子,擺放在一個大房間的地面上,我們應(yīng)該怎么擺放呢。

之所以要根據(jù)這個demo來設(shè)想這種場景,是因為我覺的,不管代碼怎么寫,但是基本的道理都不復(fù)雜,正所謂大道至簡,(畢竟我們不是做那些高大上的算法的)。自己根據(jù)場景去設(shè)想怎么擺放,同樣的也有助于理解代碼。
假設(shè)我們按照 LinearLayout豎直方式的規(guī)則來擺放,那么開始擺放第一個盒子肯定從房間的左上角開始計算位置,根據(jù)父容器padding,本身的margin等來決定具體擺放的坐標(biāo)。然后再在豎直方向上 向前推進擺放第二個盒子。

我并不知道上面那段文字有沒有能清楚的表達出我的意思, 但是這是我前段時間關(guān)于這個layout的思考過程。思考過程本身就很難表達,但是我想這樣思考是有助于理解這個過程的。

和measure過程一樣,layout過程也是一個遞歸過程,并且ViewGroup類本身不 應(yīng)該具體實現(xiàn)onLayout,具體實現(xiàn)過程應(yīng)該放在Framelayout,LinearLayout,RelativieLayout等容器類中。

當(dāng)layout過程完成之后,我們就可以得到View的左上角和右下角的坐標(biāo)了。(就是left,top,right,bottom)。值得注意的是這個坐標(biāo)是相對坐標(biāo),就是相對于View父容器的坐標(biāo)。所以通過遞歸來計算是最自然最方便的方式。

這幾天看了關(guān)于編程語言歷史方面的文章, 在早期的編程語言中,是沒有遞歸這個概念的,是后來有人想出了這個概念,才逐漸的在各種編程語言中實現(xiàn)。所以說現(xiàn)在我們來看稀奇平常很自然的概念,但是當(dāng)初第一個想出這個概念的人,那就是天才了。

下面我們就從源碼角度來進行分析。
performTraversals調(diào)用了PerformLayout方法。

 //ViewRootImpl.java  www.yaoxiaowen.com
 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,            int desiredWindowHeight) {      
        //...
        final View host = mView;        //...
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());        //...}

host其實就是DecorView了,在執(zhí)行layout時,注意傳遞的頭兩個參數(shù)都是0,這說明 整個layout過程是從屏幕坐標(biāo)系的 左上角開始執(zhí)行的。

下一步還是走到了View#Layout()View#Layout方法如下:

//View.java     www.yaoxiaowen.com
  public void layout(int l, int t, int r, int b) {        //...

        int oldL = mLeft;        int oldT = mTop;        int oldB = mBottom;        int oldR = mRight;        //setOpticalFrame方法內(nèi)部還是調(diào)用了 setFrame()方法,所以無論如何,最終都會執(zhí)行setFrame()方法。
        // setFrame()方法會將View新的left,top,right,bottom存儲到View的成員變量中,并且返回一個boolean值,
        //返回true,則表示View的位置或尺寸發(fā)生了變化;否則就是未發(fā)生變化。
        boolean changed = isLayoutModeOptical(mParent) ?                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {            //如果View布局發(fā)生了變化,或者存在 PFLAG_LAYOUT_REQUIRED 標(biāo)記,則執(zhí)行以下代碼
            //首先 觸發(fā)onLayout 方法,View中默認(rèn)的onLayout是個空方法。
            //(因為擺放位置是父容器負(fù)責(zé)的,View中存在該方法是為了遍歷循環(huán)的需要)。
            //但是 容器類的ViewGroup則需要實現(xiàn)onLayout方法,從而在 onLayout()方法中依次循環(huán)子View,
            //并調(diào)用他們的Layout方法。
            onLayout(changed, l, t, r, b);          //...

            //我們可以通過 View的addOnLayoutChangeListener(View.onLayoutChangeListener)方法
            //向View 中添加多個 Layout 發(fā)生變化的事件監(jiān)聽器。
            //這些監(jiān)聽器都存儲在 mListenerInfo.mOnLayoutChangeListeners 這個List當(dāng)中

            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnLayoutChangeListeners != null) {                // 首先對 mOnLayoutChangeListeners 中的事件監(jiān)聽器 進行拷貝。
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();                int numListeners = listenersCopy.size();                for (int i = 0; i < numListeners; ++i) {                    // 遍歷注冊的事件監(jiān)聽器,依次調(diào)用其 onLayoutChange 方法,這樣 Layout事件監(jiān)聽器就得到了相應(yīng)。
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }       //....
    }

setFrame()方法的作用也不復(fù)雜。

//View.java           www.yaoxiaowen.com// setFrame()方法會將View新的left,top,right,bottom存儲到View的成員變量中,并且返回一個boolean值,//返回true,則表示View的位置或尺寸發(fā)生了變化;否則就是未發(fā)生變化。protected boolean setFrame(int left, int top, int right, int bottom) {    //...
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
    mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);    //...}

通過分析以上代碼可以知道,在layout()方法當(dāng)中,主要做的就是將left,top,right,bottom方法保存起來,而View自身怎么被布局(其實我覺的用擺放這個詞更合適)。則是父容器需要完成的工作。

因為總所周知的原因(其實就是各個容器類ViewGroup布局子元素的方式差異很大)。ViewGroup并沒有實現(xiàn)具體的onLayout方法,各個具體的ViewGroup,比如LinearLayout,RelativeLayout,FrameLayout等,它們都有它們具體的 onLayout實現(xiàn)方式。
我們以LinearLayout為例來進行分析。

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

和measure過程是一樣的套路,也是分為兩種不同的情況,我們就以 豎直方向為例。(源碼比較多,并且實際也要考慮使用layout_weight的情況,不過我們僅僅只抽取一部分分析基本原理)。

    //LinearLayout.java      www.yaoxiaowen.com    
    void layoutVertical(int left, int top, int right, int bottom) {        final int paddingLeft = mPaddingLeft;        int childTop;        int childLeft;        //...

        //遍歷子元素
        for (int i = 0; i < count; i++) {            final View child = getVirtualChildAt(i);            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {                //經(jīng)過了measure過程,我們已經(jīng)知道了 View的寬高
                final int childWidth = child.getMeasuredWidth();                final int childHeight = child.getMeasuredHeight();                
                //布局文件中,可能設(shè)置了 margin等
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();                
               //...

                //隨著遍歷過程,childTop是逐步增加的。所以說在豎直方向上,后續(xù)的View都被擺放在靠下的位置。
                childTop += lp.topMargin;                //在設(shè)置子View時,傳遞的是 子View 左上角的坐標(biāo)和寬高尺寸
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);                //childTop逐步的向下增加,并且在增加的過程中,也考慮了 子View的margin,以及偏移量。 
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

在該方法中,通過調(diào)用setChildFrame來為子元素指定相應(yīng)的位置。
setChildFrame只是很簡單的一句話。

private void setChildFrame(View child, int left, int top, int width, int height) {        
    child.layout(left, top, left + width, top + height);
}

child.layout其實不就是繼續(xù)調(diào)用 onLayout之類的嘛。所以這樣就形成了完美的遞歸。直到把整個View樹都完成了layout過程。

自此,我們就分析完了layout過程,相比與measure過程,簡單了很多。并且理解起來也更容易,就想想在一個大房間的地面上,我們怎么使用遞歸的方式來擺放很多的盒子。

最后再來看看幾個方法。

  public final int getWidth() {        return mRight - mLeft;
    }    public final int getHeight() {        return mBottom - mTop;
    }    
     public final int getMeasuredHeight() {        return mMeasuredHeight & MEASURED_SIZE_MASK;
    }     public final int getMeasuredWidth() {        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

同樣都是得到寬高的方式,這兩組方法有什么區(qū)別呢?現(xiàn)在我們了解了Measure和layout過程,那么我們就知道了,getMeasuredWidth()方法是在measure過程完成之后可以調(diào)用的,而getWidth()方法則是在 layout過程之后可以調(diào)用的。并且?guī)缀踉谒星闆r下,兩者的返回值都是相等的。

http://www.cnblogs.com/yaoxiaowen/p/7141855.html