首先,Measure流程 是爲了測量,並計算view的大小.寬mMeasuredWidth
,高mMeasuredHeight
,然後將寬高保存.爲後續layout 和draw 提供數據支撐.
在閱讀本文之前, 請參照view和ViewGroup源碼.
數值保存MeasureSpec
父容器的layoutParams會確認MeasureSpec,即view的測量模式和大小
MeasureSpec包含一個32位的int值,高2位代表SpaceMode,低30位代表SpecSize.
MeasureSpec有三類.view會有兩個MeasureSpec變量,分別爲widthMeasureSpec,heightMeasureSpec.
以下結論會在getChildMeasureSpec
中得到驗證
1. EXACTLY
:父容器已經測量出所需要的精確大小,這也是childview的最終大小——match_parent,精確值.
ATMOST
: child view最終的大小不能超過父容器的給的——wrap_content .UNSPECIFIED
: 不確定,源碼內部使用——-一般在ScrollView,ListView .
MeasureSpace大多數情況下是由父佈局的MeasureSpace和自己的Layoutparams確定(當然,還有margin,padding).詳情見viewgroup.getChildMeasureSpec()
View
View的關鍵方法
2. measure
父佈局會在自己的onMeasure方法中,調用child.measure
,這就把measure過程轉移到了子View中。
3. onMeasure
子View會在該方法中,根據父佈局給出的限制信息,和自己的content大小,來合理的測量自己的尺寸。
4. setMeasuredDimension
當View測量結束後,把測量結果保存起來,具體保存在mMeasuredWidth和mMeasuredHeight中。
View的測量過程
measure()
–>onMeasure()
–>setMeasuredDimension()
viewGroup
viewGroup的關鍵方法
1. getChildMeasureSpec(父容器space,padding,一般是父容器的layoutparam.width或heigh)
爲child計算MeasureSpec。該方法爲每個child的每個維度(寬、高)計算正確的MeasureSpec。目標就是把當前viewgroup的MeasureSpec和child的LayoutParams結合起來,生成最合理的結果。
//主代碼
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
一段通俗易懂的getChildMeasureSpec
僞代碼
public static int getChildMeasureSpec(int 限制信息中的模式, int padding, int layoutparam.width或heigh) {
獲取限制信息中的尺寸和模式。
switch (限制信息中的模式) {
case 當前容器的父容器,給當前容器設置了一個精確的尺寸:
if (子View申請固定的尺寸LayoutParams) {
你就用你自己申請的尺寸值就行了;
} else if (子View希望和父容器一樣大) {
你就用父容器的尺寸值就行了;
} else if (子View希望包裹內容) {
你最大尺寸值爲父容器的尺寸值,但是你還是要儘可能小的測量自己的尺寸,包裹你的內容就足夠了;
}
break;
case 當前容器的父容器,給當前容器設置了一個最大尺寸:
if (子View申請固定的尺寸) {
你就用你自己申請的尺寸值就行了;
} else if (子View希望和父容器一樣大) {
你最大尺寸值爲父容器的尺寸值,但是你還是要儘可能小的測量自己的尺寸,包裹你的內容就足夠了;
} else if (子View希望包裹內容) {
你最大尺寸值爲父容器的尺寸值,但是你還是要儘可能小的測量自己的尺寸,包裹你的內容就足夠了;
}
break;
case 當前容器的父容器,對當前容器的尺寸不限制:
if (子View申請固定的尺寸) {
你就用你自己申請的尺寸值就行了;
} else if (子View希望和父容器一樣大) {
父容器對子View尺寸不做限制。
} else if (子View希望包裹內容) {
父容器對子View尺寸不做限制。
}
break;
} return 對子View尺寸的限制信息;
}
這個就是對應的結論圖,前三項是方法參數,後兩個爲計算得到的值.
還有這個結論
EXACTLY
:父容器已經測量出所需要的精確大小,這也是childview的最終大小——match_parent,確定值(不管你是0還是多少).ATMOST
: child view最終的大小不能超過父容器的給的——wrap_content .UNSPECIFIED
: 不確定,源碼內部使用——-一般在ScrollView,ListView (我們一般用match_parent, wrap_content,還有確定值,這個不用).
2. measureChildren
讓所有子view測量自己的尺寸,需要考慮當前ViewGroup的MeasureSpec和Padding。跳過狀態爲gone的子view.
3. measureChild
測量單個view的尺寸.需要考慮當前ViewGroup的MeasureSpec和Padding.
4. measureChildWithMargins
測量單個View,需要考慮當前ViewGroup的MeasureSpec和Padding、margins.
viewGroup的測量流程
measureChildren()
–> getChildMeasureSpec()
–>child.measure()
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);//測量子view
}
綜合流程
一個app啓動之後,view樹的measure過程從根節點DecordView
開始,也就是從ViewGroup開始.
一般而言
首先從viewgroup. 測量自身
measure()
,然後measureChildren()開始,遵循viewgroup的測量流程,measure()
–>measureChild()測量子view
–>getChildMeasureSpec()爲child計算測量模式
–>child.measure()子view開始測量
.子view如果是viewgroup,則重複1,如果是view,則遵循view的測量流程
child.measure()
–>measure()
–>onMeasure()
–>setMeasuredDimension()保存尺寸
.遍歷到最後一層,最後一個view.
- 把子view的尺寸告訴父佈局,讓父佈局重新測量大小.
在measure過程中,ViewGroup會根據自己當前的狀況,結合子View的尺寸數據,進行一個綜合評定,然後把相關信息告訴子View,然後子View在onMeasure自己的時候,一邊需要考慮到自己的content大小,一邊還要考慮的父佈局的限制信息,然後綜合評定,測量出一個最優的結果。
measure實踐
需求
我們做這樣一個view,view需要適配所有layoutParams類型.
思路
1.確定viewgroup的大小,
在onMeasure中,根據MeasuredSpace的不同,分別進行測量.
switch (widthMode) {
case MeasureSpec.EXACTLY://本容器爲match_parent或者有精確大小時,容器width大小是測量的大小
width = widthSize;
break;
case MeasureSpec.AT_MOST://本容器爲wrap_content,容器width大小是 最大子view的width大小+pading+margin
width = getWidth(widthSize, childCount);
break;
}
2.確定子view的大小
在layout中,用for讓每一個子view,都向右平移一些像素.
for (int i = 0; i < childCount; i++) { //依次 定位 每個 子view
View v = getChildAt(i);
left = i * OFFSET;
right = left + v.getMeasuredWidth();
bottom = top + v.getMeasuredHeight();
v.layout(left, top, right, bottom);
top += v.getMeasuredHeight();
}
代碼
/**
* Created by chenchangjun on 17/7/18.
*/
public class MyMeasureView extends ViewGroup {
private static final int OFFSET = 50;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int heigh = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility()==GONE){
continue;
}
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();//獲取子view的layoutParams
int childWithSpace = getChildMeasureSpec(widthMeasureSpec, 0, layoutParams.width); //獲取子view的測量模式(本容器的MeasureSpec,padding,子view的layoutParams中的width)
int childHeighSpace = getChildMeasureSpec(heightMeasureSpec, 0, layoutParams.height);
child.measure(childWithSpace, childHeighSpace); //子view進行測量
}
switch (widthMode) {
case MeasureSpec.EXACTLY://本容器爲match_parent或者有精確大小時,容器width大小是測量的大小
width = widthSize;
break;
case MeasureSpec.AT_MOST://本容器爲wrap_content,容器width大小是 最大子view的width大小+pading+margin
width = getWidth(widthSize, childCount);
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
heigh = heightSize;
break;
case MeasureSpec.AT_MOST:
heigh = getHeigh(heightSize, childCount);
break;
}
setMeasuredDimension(width, heigh);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = 0;
int right = 0;
int top = 0;
int bottom = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) { //依次 定位 每個 子view
View v = getChildAt(i);
if (v.getVisibility()==GONE){
continue;
}
left = i * OFFSET;
right = left + v.getMeasuredWidth();
bottom = top + v.getMeasuredHeight();
v.layout(left, top, right, bottom);
top += v.getMeasuredHeight();
}
}
private int getHeigh(int heightSize, int childCount) {
int heigh;
heigh = heightSize;
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
heigh = heigh + view.getMeasuredHeight();
}
return heigh;
}
private int getWidth(int widthSize, int childCount) {
int width;
width = widthSize;
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
if (view.getVisibility()==GONE){
continue;
}
int widthOffset = i * OFFSET + view.getMeasuredHeight();
width = Math.max(width, widthOffset); //此處wrap_Content取子view的最大width
}
return width;
}
public MyMeasureView(Context context) {
super(context);
}
public MyMeasureView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyMeasureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public MyMeasureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
結果
參考
http://www.cnblogs.com/nanxiaojue/p/3536381.html
http://www.cnblogs.com/xyhuangjinfu/p/5435201.html