1.View的测量流程:
1.1 MeasureSpec:
大小:32位,高两位是mode,低30位是size;
意义:父View对子View期望的宽高要求,是父View传递给子View的
mode有三种:
UNSPECIFIED:父容器不对子View进行限制,要多大给多大,不用
EXACTLY:父容器已经检测到子View需要的精确大小,对应LayoutParams中的match_parent
和xxxdp
两种情况
AT_MOST:父容器提供一个数值作为子View大小的上界,对应LayoutParams中的wrap_content
这种情况
DecorView的MeasureSpec由窗口尺寸
和其自身的LayoutParams
确定
普通View的MeasureSpec由父容器的MeasureSpec
和子View自身的LayoutParams
确定;
1.2 DecorView:
获取DecorView的MeasureSpec的过程:
//className = ViewRootImpl
childWidthMeasureSpec = getRootMeasureSpec(windowWidth, lp,width);
childHeightMeasureSpec = getRootMeasureSpec(windowHeight, lp,height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//windowSizes:屏幕尺寸
//rootDimension:MATCH_PARENT,WRAP_CONTENT,或具体数值
//这个方法就是通过屏幕尺寸和DecorView的LayoutParams计算出DecorView的MeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
1.3 普通ViewGroup:这里以FrameLayout为例
绘制过程由ViewRootImpl开始:
public class ViewRootImpl implements ViewParent,... {
View mView; //真正的root视图
private void performTraversals() {
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //间接调用了mView.measure方法
...
performLayout(lp, mWidth, mHeight); //间接调用了mView.layout方法
...
performDraw(); //间接调用了mView.draw方法
}
}
FrameLayout的measure方法:
@Override
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
...
for (int i = 0; i < count; i++) {
//遍历子view
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//考虑带margin去测量子View
//两个0是父容器中已经用掉的大小,在FrameLayout中这里一直是0
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
...
//计算子View最大的宽高
maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
...
}
}
...
//设置测量过的宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
}
measureChildWithMargins:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
//得到子View的LayoutParams
final MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
//计算子View的宽的MeasureSpec
final int childWidthMeasureSpec =
getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
//计算子View的高的MeasureSpec
final int childHeightMeasureSpec =
getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom +
lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
//测量子View
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
childWidthMeasureSpec :
// spec = 父容器的MeasureSpec
// padding = 父容器的padding
// childDimension = 子View中LayoutParams的长宽
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = View.MeasureSpec.getMode(spec);//父容器的模式
int specSize = View.MeasureSpec.getSize(spec);//父容器的宽或者高的大小
//子View的最大宽/高
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//根据父容器的测量模式
switch (specMode) {
case View.MeasureSpec.EXACTLY:
//LayoutParams.MATCH_PARENT=-1,LayoutParams.WRAP_CONTENT=-2
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = View.MeasureSpec.AT_MOST;
}
break;
case View.MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = View.MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = View.MeasureSpec.AT_MOST;
}
break;
case View.MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = View.MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = View.MeasureSpec.UNSPECIFIED;
}
break;
}
return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
这个函数的实际效果是做了这样的映射:
xxdp | match_parent | wrap_content | |
---|---|---|---|
EXACTLY | xxdp / EXACTLY | parentSize / EXACTLY | parentSize / AT_MOST |
AT_MOST | xxdp / EXACTLY | parentSize / AT_MOST | parentSize / AT_MOST |
UNSPECIFIED | xxdp / EXACTLY | 0 / UNSPECIFIED | 0 / UNSPECIFIED |
总结下就是:
1)遍历所有子View;
2)对于每个子View,获取其LayoutParams,结合三个参数:parentMeasureSpec,childPadding,childSize,计算得到每个子View的MeasureSpec;
3)计算childSpec流程,childSpecMode如何确定:
由parentSpecMode(三种)和childViewLayoutParams中的size(match_parent,content_wrap,xxdp)三种,共9种情况直接映射得到特定的specMode;
4)调用每个子View的measure(widthSpec,heightSepc)方法测量每个子View的大小;
5)遍历完成后每个子View都已经测量完成,它们的大小都已确定,最后依据所有子View占用的空间设置自己的大小setMeasuredDimension(int width,int height),完成ViewGroup的测量;
6)MeasureSpec如何决定最终的大小:如果是EXACTLY模式,那么ViewGroup的大小就直接由MeasureSpec确定好了,就是MeasureSpec中的Size;如果是AT_MOST模式,那么ViewGroup的最终大小不确定,需要先测量完子View后才能确定。
1.4 View
1)View的measure方法会调用onMeasure:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
}
2)getSuggestedMinSize():返回最小大小和背景最小大小的较大者,作为建议的最小Size(这个值多数情况下会被丢弃)
public static int getDefaultSize(int size, int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode){
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//这里View对match_parent和wrap_content做了相同的处理,如果要继承View需要自己实现wrap_content的情况
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
3)将步骤2计算得到的size设置为View的Dimension,结束;
1.5 总结:
1)越外层的ViewGroup越早得到MeasureSpec,但越晚完成测量;
2)getMeasuredDimension在测量完成后可以获取到
3) 继承View实现控件时wrap_content会导致该View的MeasureSpec是AT_MOST模式,且AT_MOST模式下效果与match_parent相同,所以继承View实现控件时要自己处理wrap_content的情况,如TextView等都对wrap_content有相应处理
2.Layout过程:
1)View的Layout过程是从ViewRootImpl开始的:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
...
final View host = mView;
//调用DecorView的layout方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
2)layout方法通过setFrame方法确定View自身的位置和大小,并调用onLayout方法;
public void layout(int l,int t,int r,int b){
...
setFrame(l,t,r,b);
...
//change = 是否需要重新布局
onLayout(changed,l,t,r,b);
}
3)onLayout方法遍历子View并调用它们的layout方法以确定各个子View的位置和大小;
//onLayout方法在View中只有空实现,只有ViewGroup需要实现onLayout方法
public void onLayout(boolean changed,int l,int t,int r,int b){
int count = getVirtualChildCount();
for(int i = 0;i < count;i++){
View child = getVirtualChildAt(i);
...
//setChildFrame方法实际上调用了child的layout方法
setChildFrame(child, childLeft, childTop, childWidth, childHeight);
}
}
4)getWidth()在layout时调用setFrame后可以获取得到
3.Draw过程:
1)View的draw过程是从ViewRootImpl开始的:
private void performDraw() {
...
mView.draw(canvas);
}
2)View.draw
public void draw(Canvas canvas) {
//绘制背景
drawBackground(canvas);
//调用onDraw
if (!dirtyOpaque) onDraw(canvas)
//绘制子控件
dispatchDraw(canvas);
//绘制前景
onDrawForeground(canvas);
return;
}
2)分发draw事件
protected void dispatchDraw(Canvas canvas) {
for (int i = 0; i < childrenCount; i++) {
child.draw(canvas, this, drawingTime)
}
}
4.其他:
View的重绘:
invalidate():调用onDraw()方法,用于主线程;
postInvalidate():通过handler调用了invalidate方法,用于子线程;
requestLayout():会调用measure和layout方法;
5.如何正确地获取控件宽高:
1.Activity或View # onWindowFocusChanged()
这个方法调用的时机是View已经绘制完成,窗口获取/失去焦点都会调用,这个时候去获取View的宽高是没有问题的,但是由于这个方法会被多次调用,所以不建议在这里添加复杂逻辑
2.View.post(Runnable)
Runnable中的逻辑会在View绘制完成后执行
3.ViewTreeObserver
ViewTreeObserver observer=view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if(observer.isAlive()){
observer.removeGlobalOnLayoutListener(this);
//获得宽高
int viewWidth=view.getMeasuredWidth();
int viewHeight=view.getMeasuredHeight();
}
}
});