measure過程結(jié)束后,視圖的大小就已經(jīng)測量好了,接下來就是layout的過程了。
正如其名字所描述的一樣,這個方法是用于給視圖進行布局的,也就是確定視圖的位置。
ViewRoot的performTraversals()方法會在measure結(jié)束后繼續(xù)執(zhí)行,并調(diào)用View的layout()方法來執(zhí)行此過程,如下所示:
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
layout()方法接收四個參數(shù),分別代表著左、上、右、下的坐標(biāo),當(dāng)然這個坐標(biāo)是相對于當(dāng)前視圖的父視圖而言的??梢钥吹剑@里還把剛才測量出的寬度和高度傳到了layout()方法中。
那么我們來看下layout()方法中的代碼是什么樣的吧,如下所示:
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;
if (mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>) 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()方法中,首先會調(diào)用setFrame()方法來判斷視圖的大小是否發(fā)生過變化,以確定有沒有必要對當(dāng)前的視圖進行重繪,同時還會在這里把傳遞過來的四個參數(shù)分別賦值給mLeft、mTop、mRight和mBottom這幾個變量。
接下來調(diào)用onLayout()方法,正如onMeasure()方法中的默認(rèn)行為一樣,也許你已經(jīng)迫不及待地想知道onLayout()方法中的默認(rèn)行為是什么樣的了。進入onLayout()方法,咦?怎么這是個空方法,一行代碼都沒有?!
沒錯,View中的onLayout()方法就是一個空方法,因為onLayout()過程是為了確定視圖在布局中所在的位置,而這個操作應(yīng)該是由布局來完成的,即父視圖決定子視圖的顯示位置。
既然如此,我們來看下ViewGroup中的onLayout()方法是怎么寫的吧,代碼如下:
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
可以看到,ViewGroup中的onLayout()方法竟然是一個抽象方法,這就意味著所有ViewGroup的子類都必須重寫這個方法。沒錯,像LinearLayout、RelativeLayout等布局,都是重寫了這個方法,然后在內(nèi)部按照各自的規(guī)則對子視圖進行布局的。
由于LinearLayout和RelativeLayout的布局規(guī)則都比較復(fù)雜,就不單獨拿出來進行分析了,這里我們嘗試自定義一個布局,借此來更深刻地理解onLayout()的過程。
自定義的這個布局目標(biāo)很簡單,只要能夠包含一個子視圖,并且讓子視圖正常顯示出來就可以了。那么就給這個布局起名叫做SimpleLayout吧,代碼如下所示:
public class SimpleLayout extends ViewGroup {
public SimpleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() > 0) {
View childView = getChildAt(0);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() > 0) {
View childView = getChildAt(0);
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
}
}
代碼非常的簡單,我們來看下具體的邏輯吧。
你已經(jīng)知道,onMeasure()方法會在onLayout()方法之前調(diào)用,因此這里在onMeasure()方法中判斷SimpleLayout中是否有包含一個子視圖,如果有的話就調(diào)用measureChild()方法來測量出子視圖的大小。
接著在onLayout()方法中同樣判斷SimpleLayout是否有包含一個子視圖,然后調(diào)用這個子視圖的layout()方法來確定它在SimpleLayout布局中的位置,這里傳入的四個參數(shù)依次是0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight(),分別代表著子視圖在SimpleLayout中左上右下四個點的坐標(biāo)。其中,調(diào)用childView.getMeasuredWidth()和childView.getMeasuredHeight()方法得到的值就是在onMeasure()方法中測量出的寬和高。
這樣就已經(jīng)把SimpleLayout這個布局定義好了,下面就是在XML文件中使用它了,如下所示:
<com.example.viewtest.SimpleLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
/>
</com.example.viewtest.SimpleLayout>
可以看到,我們能夠像使用普通的布局文件一樣使用SimpleLayout,只是注意它只能包含一個子視圖,多余的子視圖會被舍棄掉。這里SimpleLayout中包含了一個ImageView,并且ImageView的寬高都是wrap_content。現(xiàn)在運行一下程序,結(jié)果如下圖所示:

OK!ImageView成功已經(jīng)顯示出來了,并且顯示的位置也正是我們所期望的。如果你想改變ImageView顯示的位置,只需要改變childView.layout()方法的四個參數(shù)就行了。
在onLayout()過程結(jié)束后,我們就可以調(diào)用getWidth()方法和getHeight()方法來獲取視圖的寬高了。
說到這里,相信很多朋友長久以來都會有一個疑問,getWidth()方法和getMeasureWidth()方法到底有什么區(qū)別呢?
它們的值好像永遠都是相同的。
其實它們的值之所以會相同基本都是因為布局設(shè)計者的編碼習(xí)慣非常好,實際上它們之間的差別還是挺大的。
首先getMeasureWidth()方法在measure()過程結(jié)束后就可以獲取到了
而getWidth()方法要在layout()過程結(jié)束后才能獲取到。
另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設(shè)置的
而getWidth()方法中的值則是通過視圖右邊的坐標(biāo)減去左邊的坐標(biāo)計算出來的。
觀察SimpleLayout中onLayout()方法的代碼
這里給子視圖的layout()方法傳入的四個參數(shù)分別是0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight()
因此getWidth()方法得到的值就是childView.getMeasuredWidth() - 0 = childView.getMeasuredWidth() ,所以此時getWidth()方法和getMeasuredWidth() 得到的值就是相同的,但如果你將onLayout()方法中的代碼進行如下修改:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() > 0) {
View childView = getChildAt(0);
childView.layout(0, 0, 200, 200);
}
}
這樣getWidth()方法得到的值就是200 - 0 = 200,不會再和getMeasuredWidth()的值相同了。當(dāng)然這種做法充分不尊重measure()過程計算出的結(jié)果,通常情況下是不推薦這么寫的。getHeight()與getMeasureHeight()方法之間的關(guān)系同上,就不再重復(fù)分析了。
到此為止,我們把視圖繪制流程的第二階段也分析完了。
Home:返回首頁
Previous:onMeasure()
Next:onDraw()
作者:guolin
來源:CSDN
原文:https://blog.csdn.net/guolin_blog/article/details/16330267
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!