android layout过程分析,Andriod 从 0 开始自定义控件之 View 的 layout 过程 (八)
前言
在上一篇文章了,我們學(xué)習(xí)了 View 三大流程之一的 measure 過程,當 measure 過程完成后,View 的大小就測量好了。接下來就到了 layout 的過程了,layout 的過程就是用于確定 View 的位置。下面通過查看源碼,來更深入的了解下 layout 的整個過程。
源碼分析
View 的 layout 過程是從 ViewRoot 開始的,ViewRoot 的 performTraversals 方法會在 measure 過程執(zhí)行完后繼續(xù)執(zhí)行 performLayout 方法,在該方法中又執(zhí)行了 View 的 layout 方法:
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());復(fù)制代碼
該方法接收四個參數(shù),分別是 left、top、right、bottom 四個坐標,代表 View 的四個頂點位置,其中 left、top 的參數(shù)為 0,right、bottom 則把測量好的寬、高傳入了進來,下面繼續(xù)看下 layout 方法的具體實現(xiàn):
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;
}
// 記錄 View 的原始位置
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 判斷 isLayoutModeOptical 方法的返回值, true 則執(zhí)行 setOpticalFrame 方法,否則執(zhí)行 setFrame
// setOpticalFrame 方法最終也走了 setFrame 方法,所以最終都會執(zhí)行 setFrame 方法
// setFrame 方法會判斷 View 位置是否發(fā)生改變, 如果發(fā)生改變, 會將 View 新的 left、top、right、bottom 值賦值給成員變量,并返回一個 boolean 值,表示位置是否改變
// 當 setFrame 完成后,表示 View 本身的位置已經(jīng)確定
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 如果位置發(fā)生改變,執(zhí)行 onLayout 方法,該方法在 View 中是個空實現(xiàn),需要繼承 ViewGroup 的類實現(xiàn)
// onLayout 方法的作用是 ViewGroup 確定子 View 的位置
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList listenersCopy =
(ArrayList)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;
}復(fù)制代碼
layout 方法首先通過調(diào)用 setFrame 方法判斷 View 的位置是否發(fā)生改變,如果發(fā)生改變,則將新位置的值 left、top、right、bottom 賦值給 mLeft、mTop、mRight、mBottom 這幾個成員變量,這四個值保存著 View 的位置信息,我們可以通過 getLeft、getTop、getRight、getBottom 方法獲取到。
所以我們?nèi)绻胍玫?View 的位置信息,那么必須在 setFrame 方法執(zhí)行完畢后獲取,比如說在 onLayout 方法中獲取,因為通過看源碼我們已經(jīng)知道,onLayout 方法是在 setFrame 方法之后執(zhí)行的。
當 setFrame 方法執(zhí)行完成后,會返回一個 View 位置是否發(fā)生改變的 boolean 值,如果發(fā)生改變,那么就會走 onLayout 方法,該方法在 ViewGroup 中調(diào)用,用于確定子 View 的位置。
由于具體每種布局的實現(xiàn)效果都不同,所以 onLayout 的默認實現(xiàn),和上一篇 measure 測量過程中 ViewGroup 的 onMeasure 方法一樣,是空實現(xiàn),具體怎么確定子 View 的位置,由 ViewGroup 的具體實現(xiàn)類去作不同實現(xiàn)。
下面,我們可以自定義一個只能包含一個 View 的布局,來實現(xiàn)下 onLayout 方法,有興趣的同學(xué)也可以自己去看看 LinearLayout 等布局的 onLayout 實現(xiàn)。
實例 (單布局)
代碼實現(xiàn):
public class SingleLayout extends ViewGroup {
public SingleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(getChildCount() > 0){
View childAt = getChildAt(0);
measureChild(childAt, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(getChildCount() > 0){
View childAt = getChildAt(0);
childAt.layout(0, 0, childAt.getMeasuredWidth(), childAt.getMeasuredHeight());
}
}
}復(fù)制代碼
該自定義布局很簡單,首先在 onMeasure 方法中判斷是否有子 View,如果有,那么只測量第一個 View。接著在 onLayout 中判斷是否有子 View,如果有,則調(diào)用了第一個子 View 的 layout 方法,分別傳入 0, 0, childAt.getMeasuredWidth(), childAt.getMeasuredHeight() 四個參數(shù),這四個參數(shù)分別代表了子 View 在 當前自定義布局中的位置,也就是放置在當前自定義布局的左上角。
當子 VIew 在 layout 方法中確定了自己的位置后,如果子 View 是 ViewGroup 那么又會去調(diào)用 onLayout 方法去確定它子 View 的位置。這樣一層一層傳遞下去,就完成了整個 View 數(shù)的 layout 過程。
下面我們把剛剛寫的布局運行下,看下效果:
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World2!"/>
復(fù)制代碼運行結(jié)果:
可以看到,我們盡管在布局下面放了兩個 View,但最終顯示的只有一個 View。
getMeasuredWidth 和 getWidth 的區(qū)別
getMeasuredWidth / getMeasuredHeight 的值是在 onMeausre 方法結(jié)束后可以獲取到的,getWidth / getHeight 的值是在 onLayout 方法結(jié)束后可以獲取到的。
通過查看 View 的 getWidth 、getHeight 源碼:
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}復(fù)制代碼
結(jié)合 mLeft、mTop、mRight、mBottom 這四個成員變量的賦值過程來看,getWidth 方法的返回值剛好就是 getMeasuredWidth,因為 getMeasuredWidth - 0 不就還是 getMeasuredWidth 嗎。
但是在某些特殊情況下,還是會導(dǎo)致兩者的返回值不相同,比如說以剛剛自定義的 SingleLayout 的例子看來,我如果將調(diào)用子 View 的 layout 方法修改為:
childAt.layout(100, 100, childAt.getMeasuredWidth(), childAt.getMeasuredHeight());復(fù)制代碼
那么最終獲取的結(jié)果,會發(fā)現(xiàn) getWidth 的值比 getMeasuredWidth 的值少 100px。雖然這樣做沒有啥意義,但是證明了測量寬高并不一定會等于最終的寬高。
還有一種情況,就是當 View 需要多次測量才能確定自己的測量寬高時,那么在前幾次的測量過程中得出的測量寬高有可能并不等于最終的測量寬高,這時獲取的測量寬高并不一定與最終寬高相等。
參考
總結(jié)
以上是生活随笔為你收集整理的android layout过程分析,Andriod 从 0 开始自定义控件之 View 的 layout 过程 (八)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux统一编程接口,restful接
- 下一篇: MATLAB接收机位置解算,GPS-re