一抠藕、View的繪制流程
View的繪制流程分為三部分饿肺,首先是進行Measure,也就是測量View的寬高,然后是Layout,確定View的頂點以及寬高盾似。Draw就是講View繪制到屏幕上敬辣。
這三個流程是通過 ViewRoot完成的,ViewRoot的performTraversals方法會依次調(diào)用performMeasure零院、performLayout溉跃、performDraw三個方法。告抄,這三個方法又會調(diào)用頂級View各自的measure撰茎、layout、draw方法,其中measure會調(diào)用onMeasure方法打洼,對所有的子元素進行measure乾吻。子元素又會重復(fù)父容器的measure過程,以此完成整個View樹的遍歷拟蜻。
下圖可以幫助我們理解
上面我們提到的ViewRoot對應(yīng)于ViewRootImpl類,它是連接WindowManager和DecorView的紐帶枯饿。什么是DecorView呢酝锅?DecorView是一個頂級View,它的內(nèi)部一般包含了一個豎直方向的LinearLayout,布局里上部分是標(biāo)題欄奢方,也就是actionBar搔扁,下部分是內(nèi)容欄。我們在activity的onCreate方法setContentView是把我們的布局加載到內(nèi)容欄里蟋字。我們看看下面這張圖
二稿蹲、理解MeasureSpec
在了解measure過程之前,我們需要了解MeasureSpec這個重要的類鹊奖。MeasureSpec很大程度決定了View的尺寸苛聘,但又不是決定的,因為View的尺寸還與父容器的大小有關(guān)。在測量過程中设哗,系統(tǒng)會將View的LayoutParams根據(jù)父容器的規(guī)格轉(zhuǎn)換成對應(yīng)的MeasureSpec唱捣,通過這個MeasureSpec測量出View的寬高。這句話應(yīng)好好理解一下网梢。
1震缭、MeasureSpec、SpecMode战虏、SpecSize
MeasureSpec是代表一個32位int值拣宰,高2位代表SpecMode,低30位代表SpecSize。SpecMode表示測量模式烦感,SpecSize指的是規(guī)格大小巡社。同樣SpecMode和SpecSize也是一個int值。這里的MeasureSpec指的是它代表的int值啸盏,而不是指MeasureSpec這個類重贺。
SpecMocde分為三種
- UNSPECIFIED
父容器不對View做現(xiàn)在,這種情況一般用于系統(tǒng)內(nèi)部 - EXACTLY
父容器已經(jīng)檢測出View所需的大小回懦,這時View的最終大小對應(yīng)的是SpecSize的值气笙。它對應(yīng)于LayoutParams的的match_parent和具體的數(shù)值 - AT_MOST
父容器指定了一個可用大小即SpecSize,View的大小不能大于這個值怯晕,它對應(yīng)LayoutParams的wrap_content
對于DecorView潜圃,其MeasureSpec由窗口的尺寸和自身的LayoutParams決定,對于普通View舟茶,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams決定谭期。MeasureSpec一旦確定,onMeasure方法就可以確定View的寬高吧凉。
下表是普通View的MeasureSpec創(chuàng)建規(guī)則
三隧出、measure過程
自定義View如果只是一個View,則通過measure便可完成測量過程,若是ViewGroup,則需通過遍歷完成所有子元素的measure過程阀捅。
- View的measure過程
View的measure方法會調(diào)用onMeasure方法胀瞪。有一點要注意,View的最終大小是在layout階段確定饲鄙,這和測量出的大小有點區(qū)別凄诞,因為測量的大小并不一定是最終大小,但大多數(shù)情況兩者相等忍级。
我們來看一下onMeasure的源碼
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
我們再看看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;
}
我們只關(guān)注AT_MOST和EXACTLY兩種情況帆谍,他們的返回值是一樣的,為specSize轴咱。
當(dāng)View在布局中使用wrap_content,則查詢上表可得此時的specSize為parentSize汛蝙,也就是父容器所柿忆蹋空間的大小,這和match_parent的效果是一樣的患雇。也就是說wrap_content不起作用跃脊。這種情況出現(xiàn)在繼承View的自定義View的情況下,也就是說這時的自定義View需要重寫onMeasure方法并設(shè)置wrap_content的值苛吱。解決這個問題酪术,只要判斷SpecMode的類型并 setMeasuredDimension(mWidth,mHeight);
就可以了
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ super.onMeasure(widthMeasureSpec, heightMeasureSpec); //先按照父類的方法計算,下面就行覆蓋
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec):
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec):
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec):
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec): if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,mHeight); }
else if (widthSpecMode == AT_MOST) { setMeasureDimension(mWidth,heightSpecSize);
} else if (heightSpecMode == AT_MOST){ setMeasureDimension(widthSpecSize,mHeight);
}
}
在上面的代碼翠储,我們只要給View設(shè)置一個默認的內(nèi)部寬高并設(shè)置為wrap_content的值就可以了绘雁。
- ViewGroup的measure過程
ViewGroup除了需要完成自身的measure過程,還要遍歷子View,調(diào)用其measure方法援所。ViewGroup沒有重寫View的onMeasure方法庐舟,但它有一個measureChildren方法
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);
} }}
measureChildren方法遍歷子View,通過measureChild測量住拭。
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasure){
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight,lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom,lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}
measureChild中child調(diào)用measure方法完成measure過程挪略。
- measure過程小結(jié)
measure完成后,通過getMeasuredWidth()
和getMeasuredHeight()
即可獲得View的測量寬高滔岳。之前說到通過measure測量的寬高不一定等于View最終的大小杠娱。這是因為我們?nèi)〉玫臏y量寬高的時機不對,保險的做法是在onLayout方法中獲得谱煤,這時已經(jīng)測量完畢摊求。