View的工作流程
view的工作流程主要分为三个1.measure过程,测量view的宽度高度2.layout过程,确定位置3.draw绘制页面的,View的整个绘制过程从ViewRootImpl的performTraversals()这个是整个View绘制的入口
//伪代码只标注重要的部分
private void performTraversals() {
//这个是整个measure的入口 实际调用的是view的measure方法
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//这个layout的入口 实际调用的是view.layout()方法
performLayout(lp, mWidth, mHeight);
//整个draw的入口 实际调用的是view的draw()
performDraw();
}
1.measure过程
(1)MeasureSpec测量规格
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << View.MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
}
首先先明确这个是描述测量结果的一般用一个32位的值高位2位表示测量的mode 模式,低32位表示测量的大小,很多系统源码喜欢用移位和异或等运算表达包含两个数字这种结合,大概是为了节约内存吧
UNSPECIFIED 不限制的模式,父容器不限制当前的大小,当前容器想要多少就是多少,这个一般用于系统内部
EXACTLY 精确模式,父容器已经有明确的大小,这个时候由view的size决定,一般对应view的match_paent和具体大小这两种情况
AT_MOST 最大模式,父容器限制住最大的大小,view最大也就是这个大小,对应wrap_content
这里先简单总结出结论,具体为什么这样,下面的代码会对结论有个详细的描述
(2)view的measure
measure 方法是一个final的方法,子类不能复写,咱们先假定页面只有一个View,先分析单一view的情况,这种比较简单,从简单到复杂,分析一下整个过程
//发现这里面已经生成测量的高宽了,这样说明是从ViewGroup里面生成的高宽,这里先直接得出结论,一会分析ViewGroup的时候就能看见
public final void measure(int widthMeasureSpec, int heightMeasureSpec){
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//主要是设置高宽
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//获取建议的宽度 宽度跟这个大致相同
protected int getSuggestedMinimumWidth() {
//如果没有背景用的是android:minWidth 这个属性设置的宽度,如果有背景取 背景 和android:minWidth的比较大的值
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
/**
*
* @param size getSuggestedMinimumWidth和getSuggestedMinimumHeight的size
* @param measureSpec 获取的测量的规格,这个是由父容器提供的,ViewGroup里面可以看到
* @return
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
//根绝mode返回测量的高度 这个里面可以看到
switch (specMode) {
//系统内部测量的时候,一般用不到
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//这个时候都是测试的size
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
从上面可以看到最终得到的结果都是测量的specSize,当当前的View使用wrap_content的时候,specMode当前的是MeasureSpec.AT_MOST,使用match_parent当前的是MeasureSpec.EXACTLY,这里面可以证明自定义View的时候wrap_content 使用效果跟match_parent 一样了都是一个,所有自定义View的时候要在onMeauser里面对wrap_content的时候进行处理,设置默认的宽高,具体的代码在下面的ViewGroup看一下
(3)viewGroup的measure
如果页面显示有ViewGroup包含几个子View这种情况的时候,测量的时候也会先调用measure 这是由View实现的fianal的方法,也会调用onMeasure方法但是ViewGroup没有onMeasure的方法,网上都是直接从measureChildren方法开始的,我之前一直查找都没有调用这个方法,不知道为什么会从这里面开始,后来看自定义ViewGroup的时候才发现,要重写onMeasure()方法,这个时候会调用measureChildren()测量宽高,因为ViewGroup是个抽象的view集合,只有具体的如LinearLayout才会复写onMeasure()这个时候才有意义
//循环遍历子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);
}
}
}
//测量单个的View的高度和宽度
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final ViewGroup.LayoutParams lp = child.getLayoutParams();
//从这个方法可以看出子view的宽度跟 父view的宽度的规格和 当前view的宽度的LayoutParam决定
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
*
* @param spec 父view的测量的规格
* @param padding 间距
* @param childDimension 子view的宽度
* @return
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//父view的mode
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
从上面的一大片代码可以看到View的测量的高宽是有父view的MeasureSpec 和子view的LayoutParams共同决定的可以总结出来普通自定义view的测量的高宽了
总结:子view的size 和mode 是由父view的MeasureSpec和当前view的LayoutParams共同决定,在父view的时候已经生成,传递给子view,子View的onMeasure只不过是设置当前的高度宽度
(4)LinearLayout的measure
//根据方向分别进行测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
//核心代码
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final float childWeight = lp.weight;
if (childWeight > 0) {
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//总的高度 是每一个view的测量高度 +margin这些进行叠加
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
//设置宽高,从这里面可以看到都是先遍历完子view,然后设置ViewGroup的高度
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
}
LinearLayout里面的子View的高宽也是由父view的MeasureSpec和当前view的LayoutParams共同决定,LinearLayout的测量过程就是循环遍历所有的子View,给他一个MeasureSpec 调用view的measure过程,然后调用子View的onMeasure 设置子View的高度,然后view如果还有子View,这就是个循环遍历的过程,当所有子View测量完,设置万高宽之后然后调用自己的setMeasuredDimension 方法设定宽高,这个也是由所有的控件的高度共同决定的
2layout过程
//确定view的的位置
public void layout(int l, int t, int r, int b) {
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);
//确定子View 的位置
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
}
//设置左右上下的位置
protected boolean setFrame(int left, int top, int right, int bottom) {
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
onLayout 计算view的位置,view 没有子view所有为空方法所以看一下LinearLayout的方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
//从下面的代码可以看出LinearLayout 子view的位置在他之前子view 叠加出来的高度 +childHeight 这种方式确定左上右下的位置
//这样可以总结出一句话那就是测量的高宽一般都是最后的高宽 getWidth()和getHeight方法都是用的layout左右上下位置相减
//得到的,更这里设置的childWidth和childHeight一致
void layoutVertical(int left, int top, int right, int bottom) {
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
//调用子view的layout 设置自己的位置
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
总结:layout过程比较简单总结而言就是ViewGroup 调用layout 确定自己的位置,然后调用onLayout生成子View的位置,然后递归调用子View的layout设定位置,层层递归到最后,因为设置的right和bottom 是left+MeasuredWidth和top+MeasuredHeight 而view的getWidth和getHeight 正好是mRight - mLeft和mBottom - mTop 这样计算出来就是MeasuredWidth和MeasuredHeight,所以测量的值就是最终的width 和height 除非layout 设置的时候故意改变其他的值,导致不一致如动态加上100像素这种写法
private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width+100, top + height+100); }
3draw过程
绘制自己的过程
public void draw(Canvas canvas) {
//画自己的背景
drawBackground(canvas);
//绘制自己的content
onDraw(canvas);
// 绘制自己的子view
dispatchDraw(canvas);
// 绘制装饰 scrollbars这些
onDrawForeground(canvas);
}
绘制过程也是递归调用,先绘制自己的背景内容,递归绘制子View的背景内容
本文地址:https://blog.csdn.net/u010939317/article/details/107951885