Android的UI管理系統(tǒng)層級關(guān)系

PhoneWindow是Adroid系統(tǒng)中最基本的窗口系統(tǒng),每個Activity會創(chuàng)建一個PhoneWindow是Activity和view的系統(tǒng)交互接口。DecorView本質(zhì)上是一個framlayout。
View的繪制流程
View的繪制時從ViewRoot的performTraversals方法開始的,經(jīng)過measure、layout和draw三個過程最終將一個view繪制出來。
performTraversals會一次調(diào)用performMeasure、performLayout、performDraw三個方法,分別完成頂級View的measure、layout、draw三大流程。
其中在performMeasure會調(diào)用measure方法,measure方法又會調(diào)用onMeasure方法,在onMeasure方法中會對所有的子元素進(jìn)行measure過程,這個時候measure流程就從父容器傳遞到子元素中,這樣就完成了一次measure過程。
接著子元素會重復(fù)父容器的measure過程,如此反復(fù)完成整個View樹的遍歷。
同理,performLayout和performDraw的傳遞流程與其類似,唯一不同的是performDraw在draw方法中通過dispatchDraw來實現(xiàn)。
注意
measure決定了View的寬/高,measure完成后,可以通過下面方法來獲得View測量后的View的款和高
getMeasuredWidth();
getMeasuredHeight();
layout決定了View的四個頂點的坐標(biāo)和實際的View的寬/高完。
//通過此方法數(shù)去View的四個頂點
getTop();
getLeft();
getRight();
getBottom();
//layout后獲取最終的寬高
getWidth();
getHeight();
//view的getWidth()方法
public final int getWidth() {
return mRight - mLeft;
}
draw過程決定了View的顯示,只有draw方法完成后View的內(nèi)容才能呈現(xiàn)在屏幕上。
View的工作流程
Measure過程
1.如果只是一個原始的View,那么measure方法就完成其測量過程、
2.如果是一個viewgroup,除了要完成自己的測量過程外,還會遍歷去調(diào)用所有子元素的measure方法,各個子元素再遞歸去執(zhí)行這個流程。
1view的measure過程
View的measure過程由其measure方法來完成,measure方法是一個final類型的方法,子類不能重寫。在measure方法中會去調(diào)用View的onMeasure方法。
(view的大小最終是在layout階段確定的)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 獲取值的模式
MeasureSpec.getMode(widthMeasureSpec);
// 獲取值的大小
MeasureSpec.getSize(widthMeasureSpec);
getDefaultSize()
//setMeasuredDimension方法測量view是多寬多高的
// 測量出最終view的值(傳入多少就是多少)
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// onMeasure方法調(diào)用了setMeasuredDimension方法
setMeasuredDimension(200, 200);
// 通過這個方法來測量view是多寬多高的
// 這個方法中的參數(shù)就代表了測量之后的寬和高--對成員方法的賦值--
}
**View的onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension設(shè)置view的寬高測量值,來看getDefaultSize方法
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
可以看出在EXACTLY和AT_MOST情況下,返回的其實就是measureSpec中的specSize
如果view沒有設(shè)置背景,那么view的寬度為mMinWindth,而mMinWindth對應(yīng)于Android:minWidth這個屬性的值。如果這個屬性不指定,mMinWindth的值為0.
如果View指定了背景則View的寬度為Android:minWidth和背景的最小寬度這兩者中的最大值
max(mMinWidth,mBackGroud.getMinimumWidth())
ViewGroup的measure過程
除了完成自己的measure過程外,還會遍歷去調(diào)用所有子元素的measure方法,各個子元素再去遞歸執(zhí)行這個過程。Viewgroup是一個抽象類,沒有重寫View的onMeasure方法,但是提供了measureChildren的方法
viewgroup的測量過程的onMeasure方法需要各個子類去具體實現(xiàn)
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
measurechild的思想就是取出子元素的LayoutParams,然后通過getChildMeasureSpec來創(chuàng)建子元素的MeasureSpec,接著將MeasureSpec直接傳遞給View的Measure方法來進(jìn)行測量
linearlayout的measure過程
系統(tǒng)會遍歷元素對每個子元素與執(zhí)行measureChildBeforeLyout,這個方法來調(diào)用view子元素的measure過程,這樣各個子元素開始因此進(jìn)入measure過程
每測量一個子元素,mTotalLength就會增加,增加的部分主要包括了子元素的高度以及元素在豎直方向的margin。
當(dāng)子元素測量完畢后,LinearLayout會測量自己的大小
針對豎直的LinearLayout,它在水平方向的測量過程遵循View的測量過程
豎直方向:若采用的是matchparent或具體數(shù)值,測量與View過程。
若采用wrap_content,那么就是子元素的總和,但人不能超過它的父容器的剩余空間(當(dāng)然還要考慮其padding值)
注意
在某些極端情況下,系統(tǒng)可能需要多次measure才能確定最終的測量寬高,在這種情況下,在onMeasure方法中拿到的測量寬高時不準(zhǔn)確的。因此較好的習(xí)慣是在onlayout方法中取獲取測量寬和高。
在activity的onCreate、onStart、onResume中均無法正確的得到某個View的寬高信息。因為View的measure與activity的生命周期不是同步的。如果View沒有測量完則,獲取到的View是0.
獲取測量寬高的時機(jī)。
(1)Activity/View的onWindowFocusChanged
這個方法的含義是View已經(jīng)初始化完畢,寬高已經(jīng)準(zhǔn)備好了。
onWindowFocusChanged會被調(diào)用多次,當(dāng)activity獲得焦點并失去焦點均會被調(diào)用,如果onResume和onPause方法被頻繁調(diào)用,那么onWindowFocusChanged也會被調(diào)用。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
view.getMeasuredWidth();
view.getMeasuredHeight();
}
}
(2)view.post(runnable)
通過post可以將一個runnable投遞到消息隊列的尾部,然后等待Looper調(diào)用此runnable的時候,View此時已經(jīng)初始化好了
@Override
protected void onStart() {
super.onStart();
view.post(new Runnable(){
@Override
public void run() {
view.getMeasuredWidth();
view.getMeasuredHeight();
}
});
}
(3)ViewTreeObserver
使用ViewTreeObserver的眾多接口可以完成此功能,如addOnGlobalLayoutListener,當(dāng)view樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的可見性發(fā)生改變時onGlobalLayout會被調(diào)用。
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
view.getMeasuredWidth();
view.getMeasuredHeight();
}
});
(4)手動對view進(jìn)行measure來得到View的寬和高
根據(jù)View的layoutparams來分
matchparent
無法得出具體結(jié)果,根據(jù)View的measure過程,構(gòu)造此種Measure需要知道parentSize,即父容器的剩余空間,而這個時候無法知道父容器的剩余空間,因此理論上無法測量出。
具體的數(shù)值
int width = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heght = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
view.measure(width,heght);
wrapcontent
view的尺寸最大是1 << 30) - 1==2^30-1
int width = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
int height = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
view.measure(width,heght);
理解MeasureSpec
在測量過程中系統(tǒng)會將View的LayoutParams根據(jù)父容器所施加的規(guī)則轉(zhuǎn)換成對應(yīng)的MeasureSpec,然后更具這個measureSpec來測量出View的寬和高
widthMeasureSpec :寬測量規(guī)格 :父容器對view的寬度的期望
heightMeasureSpec:高的測量規(guī)格:父容器對view高度期望
他兩的值實際上是由兩部分組成(32位):前30位表示大小+后兩位來表示mode(模式) **
SpecMode和SpecSize打包成一個MeasureSpec,而SpecMode可以通過解包的形式來得出原始的SpecMode和SpecSize。
SpecMode有三個類
1)UNSPECIFIED** 未指定(爹不管模式), 對子View的最大值沒有要求 ,一般用于系統(tǒng)內(nèi)部。
2)EXACTLY:精確的 父容器已經(jīng)檢測出View所需要的精確大小,這個時候最終大小就是SpecSize所指定的值。對應(yīng)于LayoutParams中的match_parent 和具體的數(shù)值。
3)AT_MOST :至多 父容器指定了一個可用大小即SpecSize,View的值不能大于這個值,具體指要看不同View的具體實現(xiàn)。它對應(yīng)于LayoutParams中的wrap_content。
在View測量的時候,系統(tǒng)會將LayoutParams在父容器的約束下轉(zhuǎn)換成對應(yīng)的MeasureSpec來確定View測量后的寬/高
1.對于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams來共同確定。
2.對于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同決定,MeasureSpec一旦確定,onMeasure中就可以確定View測量的寬/高。
在對子元素進(jìn)行measure,在調(diào)用子元素的measure方法之前會先通過getChildMeasureSpec()方法來獲得子元素的MeasureSpec。

Layout過程
Layout的作用是ViewGroup用來確定子元素的位置
**layout方法確定view本身的位置,而onLayout方法確定所有子元素的位置。
當(dāng) ViewGroup的位置被確定后它在onLayout中會遍歷所有的子元素并調(diào)用其Layout方法,在layout方法中onLayout方法又會被調(diào)用。
View的layout方法
layout方法的大致流程:
首先通過setFram方法來設(shè)定View的四個頂點的位置,即初始化mLeft,mRight,mTop,mBottom四個值,這四個值已確定,那么View在父容器的位置就確定了。接著會調(diào)用onLayout方法。
View和ViewGroup中的onlayout方法。
這個方法的用途是確定子元素的位置。
和onMeasure方法一樣onlayout實現(xiàn)同具體的布局有關(guān),
view和Viewgroup中均沒有真真的實現(xiàn)onLayout方法,
**LinearLayout的layoutVertical
遍歷所有子元素并調(diào)用setChildFrame方法來為子元素指定對應(yīng)的位置,childTop的值越拉越大,后面的子元素會越靠下方。
setChildFrame僅僅是調(diào)用子元素的layout方法而已。
這樣在layout方法中完成自己的定位以后,就通過onLayout方法去調(diào)用子元素的layout方法,這樣就會一層一層的傳遞下去。
draw過程
它的作用是將View繪制到屏幕上面。
View的繪制過程遵循如下幾步
(1)繪制背景drawBackground.(canvas)
(2)繪制自己(ondraw)
(3)繪制children(dispatchDraw)
(4)繪制裝飾(onDrawScrollBars)
view的繪制過程是通過dispatchDraw來實現(xiàn)的,dispatchDraw會遍歷所有子元素的draw方法,如此draw事件就傳遞下去了
參考
《Android高級進(jìn)階》
《Android開發(fā)藝術(shù)探索》