View的工作流程主要指measure,layout,draw三大流程:
View的measure过程:View的measure过程由其measure方法来完成,measure方法是一个final类型的方法因此子类不能重写此方法,在View中measure方法会调用View的onMeasure方法,因此只需看onMeasure的实现即可.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension方法会设置View宽高的测量值即getDefaultSize方法或取到的值:
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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;
}
由于UNSPECIFIED一般用于系统测量,我们只需要注意AT_MOST,EXACTLY两种情况.即getDefaultSize返回的大小是measureSpec中specSize,这个specSize就是View的测量后的大小,因为View的最终大小是在layout阶段确定的所以只能说是测量后的大小,但是几乎所有情况下View的测量大小和最终大小是相等的.
在UNSPECIFIED情况下View的大小为getDefaultSize的第一个参数即getSuggestedMinimumWidth和getSuggestedMinimumHight两个方法的返回值
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
如果View没有设置背景,View的宽度为mMinWidth,mMinWidth的值对应于android:minWidth这个属性所指定的值,如果没有指定则默认值为0;如果View有背景,则View的宽度为max(mMinWidth,mBackground.getMinimumWidth()).
Drawable的getMinimumWidth方法
public int getMinimumHeight() {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
getMinimumWidth返回的是Drawable的原始高度,如果没有原始高度则返回0,ShapeDrawable无原始宽高,BitmapDrawable有原始宽高.
即getSuggestedMinimumWidth方法可总结为:如果View没有背景返回android:minWidth属性值,值可为0;如果View有背景则返回android:minWidth和背景的最小宽度这两者中最大的值,在UNSPECIFIED情况则下返回View测量宽高.
getDefaultSize方法可以看出View的大小是由specSIze决定的,如果直接继承VIew的自定义控件要重写onMeasure方法wrap_content的大小,否则布局中wrap_content就相当于match_parent,因为如果View布局中使用wrap_content,他的specMode为At_MOST模式,对应的specSize为parentSize纪委父容器中目前可以使用的大小也是父容器中剩余空间的大小,此时View的宽高等于父容器当前剩余空间的大小,这种情况和布局中使用match_parent完全一致,解决方法:
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 == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,mHeight);
}else if(heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,heightSpecSize);
}
}
mWidth和mHeight根据需要灵活指定.
ViewGroup的Measure过程:
ViewGroup除了完成自己的measure过程还要遍历调用所有子元素的measure方法,各个子元素再递归执行这个过程,和View不同ViewGroup是一个抽象类,因此没有重写View的onMeasure方法,但是提供了一个measureChildren的方法
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
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方法:
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
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);
}
measureChild逻辑即为取出子元素的LayoutParams,再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec传递给View的measure方法来进行测量.
ViewGroup没有定义其具体的测量过程是由于ViewGroup是一个抽象类,onMeasure方法需要其子类去具体实现,以LinearLayout的onMeasure方法分析ViewGroup的measure过程:
LinearLayout的onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
measureVertical方法大概逻辑为遍历子元素对每个子元素执行measureChildBeforeLayout方法,这个方法内部会调用子元素的measure方法,这样子元素开始进入measure过程,并且系统会通过mTotalLength这个变量存储LinearLayout在竖直方向的初步高度.梅测量一个子元素mTotalLength都会增加,包括子元素的高度和子元素在竖直方向上的margin等值,测量完子元素后,LineraLayout会测量自己的大小.对于竖直方向的LinearLayout,它的水平方向遵循View的测量过程,在竖直方向如果是match_parent或者具体数值,它的测量过程会和View的测量一致,即高度为specSize;如果布局中采用的是wrap_content,那么它的高度为所有子元素说占用高度的总和,但仍不能超过它的父容器的剩余空间,它的高度最终还要考虑其竖直方向上的padding参考源码
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
* optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
* size is smaller than the size the view wants to be.
*
* @param size How big the view wants to be
* @param measureSpec Constraints imposed by the parent
* @return Size information bit mask as defined by
* {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
*/
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
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:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState&MEASURED_STATE_MASK);
}
一般情况下在measure完成后即可通过getMeasureWidth/Height获取到正确的View的测量宽高,但是在特殊情况下系统可能通过多次的measure才能最终确定宽高,这种情况下onMeasure方法中拿到的测量宽高可能不准确,因此比较好的是在onLayout方法中获取View的最终宽高.
View的宽高获取方法
1,onWindowFocusChanged方法含义为View已经初始化完毕,需注意onWindowFocusChanged会被多次调用,当Activity的窗口得到或失去焦点都会被调用一次,如果频繁的进行onResume和onPause,onWindowFocusChanged会被频繁调用示例代码如下:
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
2,view.post(runnable)
通过post可以将一个runnable投递到消息队列的尾部,然后等待looper调用此runnable,此时View已经初始化完成,示例代码如下:
protected void onStart() {
super.onStart();
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
3.ViewTreeObserver
使用ViewTreeObserver的众多回调接口完成此功能,例如OnGlobalLayoutListener,当View树的状态发生改变或者View树内部的View可见性发生改变时,onGlobalLayout方法会被回调,伴随着View树的状态改变onGlobalLayout会被多次调用,示例代码如下:
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
4,view.measure(int widthMeasureSpec , int heightMeasureSpec).
match_parent无法measure出具体宽高,构造MeasureSpec需要知道parentSize而此时无法知道父容器的剩余空间即parentSize
具体数值(dp/px)例如宽高都为100px获取方法如下:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
wrap_content
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
View的尺寸使用30位二进制表示,也就是最大是30个1(2^30-1),即(1<<30)-1,在最大化的模式下使用View理论上能支持最大值构造MeasureSpec.