文章目录
View的工作流程
整体流程
每个Activity包含一个PhoneWindow对象,PhoneWindow时Activity和View交互的接口,DecorView本质是一个FrameLayout,所最顶层的View。
MeasureSpec测量规格
说明
MeasureSpec指View的测量规格,MeasureSpec是View的一个静态内部类。
MeasureSpec是一个32位的整数型:高2位表示测量模式SpecMode,低30位表示测量尺寸SpecSize,共同组成MeasureSpec。
子View的MeasureSpec是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec共同计算出来的,具体逻辑在getChildMeasureSpec()
里。
MeasureSpec源码分析
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/*
未指定模式:
父View不限制子View大小,子View想要多大就多大
一般用于系统内部的测量,自定义View很少用到
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/*
精确模式:
子View宽高为match_parent或具体值时生效
这时View的值就是SpecSize
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/*
最大值模式:
视图宽高为warp_content时生效
子View的尺寸不能超过父View的尺寸
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
//根据尺寸和测量模式生成一个MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//获取测量模式
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//获取测量尺寸
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
//调整MeasureSpec大小
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
int size = getSize(measureSpec);
if (mode == UNSPECIFIED) {
return makeMeasureSpec(size, UNSPECIFIED);
}
size += delta;
if (size < 0) {
Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
") spec: " + toString(measureSpec) + " delta: " + delta);
size = 0;
}
return makeMeasureSpec(size, mode);
}
}
getChildMeasureSpec源码分析
public abstract class ViewGroup{
/**
* ViewGroup在测量子View时会调用measureChildWithMargins()方法,或类似方法。
* child 子View
* parentWidthMeasureSpec 父View的宽的测量规格
* widthUsed 父View在宽上的已使用空间
* parentHeightMeasureSpec 父View的高的测量规格
* heightUsed 父View在高上的已使用空间
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//获取子View的布局参数
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//获取子View的宽的测量规格
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
//获取子View的高的测量规格
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//测量子View
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
* getChildMeasureSpec() 用于获取子View的测量规格,
子View的MeasureSpec=父view的MeasureSpec值+子view的LayoutParams属性 共同
* spec:父容器的测量规格
* padding:父容器的已用空间
* childDimension:子View的尺寸
**/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取父容器的测量模式
int specMode = MeasureSpec.getMode(spec);
//获取父容器的测量尺寸
int specSize = MeasureSpec.getSize(spec);
//获取父容器的剩余空间
//通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值)
int size = Math.max(0, specSize - padding);
//子View期望的尺寸和模式(需要计算)
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
/*
当父view的测量模式为EXACITY时(即父View设置为match_parent或固定值),父view强加给子view确切的值,
*/
if (childDimension >= 0) {
/*
如果子View有固定值时,
则子View的尺寸为自身的值,模式为EXACTLY
*/
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
/*
如果子View为match_parent时,
则子View的尺寸为父View的尺寸,模式为EXACTLY
*/
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
/*
如果子View为wrap_content时,
则子View的尺寸为父View的剩余空间,但不能超过父View的尺寸,模式为AT_MOST
*/
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
/*
当父View的测量模式为AT_MOST(即父View设置为warp_content)
*/
if (childDimension >= 0) {
/*
如果子View为固定值时,
则子View的尺寸为自身的值,模式为EXACTLY
*/
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
/*
如果子View为match_parent时,
则子View的尺寸为父View的剩余空间,模式为AT_MOST
*/
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
/*
如果子View为wrap_content时,
则子View的尺寸为父View的剩余空间,模式为AT_MOST
*/
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
/*
当父View的模式为UNSPECIFIED时,父容器不对子View限制
通常用于系统空间,如ListView、ScrollView等
*/
if (childDimension >= 0) {
/*
如果子View为固定值,
则子View的尺寸为自身的值,模式为EXACTLY
*/
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
/*
如果子View为match_parent时,
则子View的尺寸为0,模式为UNSPECIFIED
*/
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
/*
如果子View为warp_content时,
则子View的尺寸为0,模式为UNSPECIFIED
*/
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
}
规则总结
规律总结:
子View尺寸 | 子View的MeasureSpec |
---|---|
具体值 | 测量模式 = EXACTLY 测量尺寸 = 自身设置的值 |
match_parent | 测量模式 = AT_MOST 测量尺寸: 1. 父容器的测量模式为EXACTLY时,测量尺寸为父容器的剩余空间 2.父容器的测量模式为AT_MOST时,测量尺寸不超过父容器的剩余空间 |
warp_content | 测量模式 = AT_MOST 测量尺寸:不超过父容器的剩余空间 |
measure流程
作用
-
测量View尺寸
-
在某些情况下,可能需要测量多次才能确定View的尺寸
-
measure后获取的宽高不是最终的尺寸,可以考虑早layout后获取最终的尺寸
测量流程
- View的测量流程:只测量自身View
- ViewGroup的测量流程:
- 先遍历测量所有子View的尺寸,会调用子View的measure()
- 再合并所有子View的尺寸进行计算,获取ViewGroup的尺寸
View的measure流程
开始测量
↓
measure():基本测量逻辑,会调用onMeasure()
↓
onMeasure():根据测量规格计算View的尺寸
↓
setMeasuredDimension():保存测量View的尺寸
↓
getDefaultSize():根据测量规格计算View的尺寸
↓
完成测量
源码分析:
View#measure()
测量的入口,父容器的测量规格被传入,接着将调用View#onMeasure()
。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//调用View#onMeasure(0)
onMeasure(widthMeasureSpec, heightMeasureSpec);
//省略
} else {
//省略
}
}
View#onMeasure()
在onMeasure()
里,调用getDefaultSize()
获取测量尺寸,再调用setMeasuredDimension()
并保存测量后的尺寸
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
View#getDefaultSize()
getDefaultSize()
通过提供的尺寸和父容器的测量规格计算出View的宽高
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;
//模式为AT_MOST和EXACTLY时,
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
View#setMeasuredDimension()
将测量后的View尺寸保存
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
// 测量后的宽高
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
ViewGroup的measure流程
开始测量
↓
measure():基本测量逻辑,会调用onMeasure()
↓
onMeasure():需要自定义,会调用measureChildren()等遍历所有子View并测量,合并所有子View的尺寸,最终计算ViewGroup的尺寸
↓
measureChildren():遍历子View,会调用measureChild()
↓
measureChild():测量子View,会调用View#measure()
↓
getChildMeasureSpec():计算子View的测量规格
↓
setMeasureDimension():保存子View测量后的尺寸
↓
完成测量
源码分析:
- ViewGroup是一个抽象类,因为不同的ViewGroup拥有不同的布局特性,所以,需要实现
onMeasure()
- ViewGroup的measure流程与View的基本一致,ViewGroup需要先遍历所有子View和测量,然后在合并所有的子View的尺寸,最终生成ViewGroup的尺寸
//ViewGroup在measure时,会遍历所有子View,先调用measureChild()
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会测量子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);
//子View的MeasureSpec由:父View的MeasureSpec + 父View的padding + 子View的尺寸
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
LinearLayout源码分析
ViewGroup没有提供onMeasure()
,而是让其子类LinearLayout实现具体的测量方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
//获取View数量
final int count = getVirtualChildCount();
//遍历所有子View
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
/*
子View为GONE不可见直接跳过,
INVISIBLE仍然会计算大小
*/
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
//记录子View是否有weight属性
totalWeight += lp.weight;
//是否使用weight属性
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
//如果LinearLayout的specMode为EXACTLY,并且子View设置了weight属性,则在这里会跳过子View的measure过程
//同时标记skippedMeasure属性为true,后面会根据该属性决定是否进行第二次measure
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
/*若LinearLayout的子View设置了weight,会进行两次measure计算,比较耗时
这就是为什么LinearLayout的子View需要使用weight属性时候,最好替换成RelativeLayout布局*/
} else {
//步骤一:测量子View,最终会调用measureChildren()
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
}
//步骤二:合并所有子View尺寸,得到ViewGroup等尺寸
//获取子View尺寸
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
//合并子View大小,包含padding、margin
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
//计算总高度
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
}
maxWidth += mPaddingLeft + mPaddingRight;
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//步骤三:保存测量后的尺寸
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);
}
mTotalLength用于存储LinearLayout的垂直方向的高度,然后遍历子元素,根据子元素的MeasureSpec模式分别计算每个子元素的高度,如果是wrap_content则将每个子元素的高度和margin垂直高度等值相加并赋值给mTotalLength得出整个LinearLayout的高度。如果布局高度设置为match_parent者具体数值则和View的测量方法一样。
layout流程
作用
- 计算View的位置,四个顶点:Left、Top、Right、bottom
布局流程
-
View:只计算自身View的位置
-
ViewGroup:
-
计算自身View的位置
-
遍历子View,并计算子View的位置
-
View的layout流程
开始计算位置
↓
layout():最终调用setFrame()确定位置
↓
onLayout():空实现
↓
完成定位
源码分析:
View#layouut()
layout()
是定位的入口,传入的四个参数分别是View的四个点的坐标,相对于它的父布局定位。
public void layout(int l, int t, int r, int b) {
//当前View的四个坐标点
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
/*
确定View的位置
isLayoutModeOptical():判断子View和父View的布局模式是否相同
setFrame()
setOpticalFrame()
*/
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);
}
}
View#setFrame()
setFrame()
主要是用于设置View的四个顶点,最终确定自身View的位置。setOpticalFrame()
最终调用setFrame()
protected boolean setFrame(int left, int top, int right, int bottom) {
//确定View的四个顶点
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
//最终调用setFrame()
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
View#onLayout()
- View在
layout()
时已经确定了位置 onLayout()
在单一View中是一个空实现
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
ViewGroupd的layout流程
开始计算位置
↓
layout():确定ViewGroup的位置
↓
onLayout():ViewGroup需要自定义,确定子View在ViewGroup中的位置
↓
遍历子View:会调用子View的layout(),确定子View的位置
↓
完成定位
LinearLayout源码分析
LinearLayout#Layout()
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);
}
}
void layoutVertical(int left, int top, int right, int bottom) {
//获取子View的数量
final int count = getVirtualChildCount();
//遍历子View
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//计算子View的尺寸
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//确定子View的位置,递归调用子View的setChildFrame
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
//childTop累加,用于设置后面的子View的位置
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
LinearLayout#setChildFrame()
setChildFrame()
方法中调用子元素的layout()方法来确定自己的位置。
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
View的绘制流程
作用
- 绘制View视图
绘制流程
- View:只绘制View本身
- ViewGroup:
- 绘制自身View
- 绘制所有子View
View的draw流程
开始绘制
↓
draw():绘制View
↓
drawBackground():绘制View的北京
↓
onDraw():绘制自身View内容
↓
dispatchDraw():空实现,因为每没有子View
↓
onDrawScrollBars():绘制装饰,如:滚动条、前景
↓
完成绘制
源码分析:
public void draw(Canvas canvas) {
int saveCount;
//绘制View的背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
//当不需绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
if (!dirtyOpaque)
//绘制View内容
onDraw(canvas);
//绘制子View
dispatchDraw(canvas);
//绘制前景
onDrawForeground(canvas);
return;
}
}
//绘制View的背景
private void drawBackground(Canvas canvas) {
//获取背景Drawable
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
//获取scrollX和scrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
//对canvas进行偏移
canvas.translate(scrollX, scrollY);
//绘制背景
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
//需要子类实现
protected void onDraw(Canvas canvas) {
}
//绘制前景:滚动条等
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
ViewGroup的draw流程
开始绘制
↓
draw():绘制View
↓
drawBackground():绘制View的背景
↓
onDraw():绘制自身View的内容
↓
dispatchDraw():绘制子View
↓
遍历子View,执行子View的draw()
↓
onDrawScrollBars():绘制装饰,如滚动条、前景
↓
完成绘制
protected void dispatchDraw(Canvas canvas) {
// 遍历子View
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < childrenCount; i++) {
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 绘制子View
more |= drawChild(canvas, transientChild, drawingTime);
}
}
}
// 绘制子View
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
常见问题
getMeasuredWidth()与getWidth()区别
- getMeasuredWidth():获得View测量的宽度,在
onMeasure()
后生成。 - getWidth():获得View最终的宽度,在
onLayout()
后生成
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getWidth() {
return mRight - mLeft;
}
感谢
https://www.jianshu.com/p/1dab927b2f36
https://www.jianshu.com/p/158736a2549d