View的工作流程是指measure榨呆、layout兔魂、draw三大流程,即策略闰挡、布局、重繪礁哄。
一.Measure過程
1.view的Measure過程
1.在onMeasure調用setMeasuredDimension( )設置View的寬和高.
2.在setMeasuredDimension()中調用getDefaultSize()獲取View的寬和高.
3.在getDefaultSize()方法中又會調用到getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()獲取到View寬和高
View的onMeasure方法()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension()方法會設置View的寬和高长酗,getDefaultSize()返回View測量后的大小(注:View的最終大小是在layout階段確定的)
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;
}
getSuggestedMinimumWidth()方法
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
如果View沒有設置背景桐绒,那么View的寬度為mMinWidth夺脾,而mMinWidth對應于android:minWidth這個屬性所指的的值,如果這個屬性不指定茉继,那么mMinWidth默認值為0咧叭;如果設置了背景則返回android:minWidth和背景的最小寬度這兩者中的最大值,getSuggestedMinimumHeight()實現(xiàn)的原理也類似烁竭。
解決在布局中使用wrap_content,效果是match_parent的問題菲茬。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec , heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, mHeight);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, heightSpceSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpceSize, mHeight);
}
}
在上面的代碼中,我們只需要給View指定一個默認的寬高(mWidth, mHeight)派撕,并在wrap_content時設置此寬/高即可婉弹。對于非wrap_content的情形,使用系統(tǒng)的測量值就行终吼,指定一個默認的寬高(mWidth, mHeight)根據(jù)需要自行指定镀赌。
2.viewGroup的Measure過程
對于ViewGroup來說,除了完成自己的Measure過程际跪,還回遍歷去調用所有子元素的measure方法商佛,各個子元素在遞歸的執(zhí)行這個過程。
ViewGroup是一個抽象類姆打,沒有重寫View的onMeasure方法良姆,但是它提供了一個measureChildren方法。這是因為不同的ViewGroup子類有不同的布局特性幔戏,導致他們的測量細節(jié)各不相同歇盼,比如LinearLayout和RelativeLayout,因此ViewGroup沒辦法同一實現(xiàn)onMeasure方法。 measureChildren方法的流程:
取出子View的LayoutParams
通過getChildMeasureSpec方法來創(chuàng)建子元素的MeasureSpec
將MeasureSpec直接傳遞給View的measure方法來進行測量
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);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
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);
}
可以查看一下LinearLayout 的onMeasure方法
Measure完成以后就可以在onLayout()通過getMeasuredWidth/Height獲取到View的測量寬高评抚,
二.Layout過程
Layout的作用是ViewGroup用來確定子元素的位置豹缀,當ViewGroup位置確定以后伯复,他就會在onLayout中遍歷所有子元素并調用其layout過程,在layout方法中onLayout方法又會被調用邢笙。layout方法確定view本身的位置啸如,onlayout方法則確定所有子元素的位置。
View的layout源碼
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;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
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<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)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;
}
layout方法的大致流程:
1.通過setFrame方法來設定View的四個頂點的位置,既初始化mLeft暴构、mTop语淘、mBottom、mRight這四個值帘不,View的四個頂點位置確認了,那么View在父容器中的位置也就確認了杨箭。
2.調用onLayout方法寞焙,父容器確定子元素的位置,(同onMeasure,View 和ViewGroup 均沒有真正實現(xiàn)onLayout互婿,而是放在具體的子類如Linearlayout 中去實現(xiàn))
View的測量寬/高和最終寬/高的區(qū)別捣郊?
這個問題可以具體為:View的getMeasuredWidth和getWidth;View的getMeasuredHeight和getHeight有什么區(qū)別
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
在日常開發(fā)中,我們認為這兩個值相等慈参。
三.draw過程
View的繪制過程遵循如下幾步:
(1)繪制背景drawBackground(canvas)
(2)繪制自己onDraw
(3)繪制childrendispatchDraw遍歷所有子View的draw方法
(4)繪制裝飾onDrawScrollBars