Android View 源码解析(一) - setContentView
Android View 源码解析(二) - LayoutInflater
Android View 源码解析(三) - View的绘制过程
LinearLayout 源码分析
measure过程
主要过程
- 根据布局方向选择measure过程分支
- 初始化相关变量
- 对View进行第一次测量
- mTotalLength的再次测量
- 二次测量部分View和对为测量的子View进行测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//判断布局方向
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
measureVertical和measureHorizontal只是布局方向上的区别 以下主要分析measureVertical方法
初始化相关变量
//mTotalLength是记录内部使用的高度也就是子View的高度和 而不是LinearLayout的高度
mTotalLength = 0;
//子视图的最大宽度(不包括layout_weight>0的子View)
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;
//子视图的最大宽度(仅包含layout_weight>0的子View)
int weightedMaxWidth = 0;
//子视图是否均为fillParent 用于判断是否需要重新计算
boolean allFillParent = true;
//权重值的总和
float totalWeight = 0;
//子View的数量(统一级别下)
final int count = getVirtualChildCount();
//高度宽度模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//子View的宽度是否需要由父View决定
boolean matchWidth = false;
boolean skippedMeasure = false;
//第几个子View的baseLine作为LinearLayout的基准线
final int baselineChildIndex = mBaselineAlignedChildIndex;
//mUseLargestChild为是否使用最大子元素的尺寸作为标准再次测量
final boolean useLargestChild = mUseLargestChild;
//子View中最高高度
int largestChildHeight = Integer.MIN_VALUE;
第一次测量
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
// 测量为null的子视图的高度
// measureNullChild() 暂时返回 0 便于扩展
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
//Visibility为Gone的时候跳过该View
// getChildrenSkipCount()方法同样返回0 便于扩展
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
//根据showDivider的值(通过hasDividerBeforeChildAt()) 来决定当前子View是否需要添加分割线的高度
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
//会将子view的LayoutParams强转为父View的LayoutParams类型
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)child.getLayoutParams();
totalWeight += lp.weight;
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
// 满足该条件的话 不需要现在计算该子视图的高度 测量工作会在之后进行
// 若子View的height=0 且weight> 0 则说明该View希望使用的是LinearLayout的剩余空间
// LinearLayout是EXACTLY模式的说明LinearLayout高度已经确定 不需要依赖子View的测量结果来计算自己 就无需测量该子View
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
//测量子View
int oldHeight = Integer.MIN_VALUE;
//当前View的height=0 且weight> 0 则说明该LinearLayout的高度需要靠子View测量(不需要的在上面分支处理了)
//将子View的高度设为-1 防止子View高度为0
if (lp.height == 0 && lp.weight > 0) {
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
//调用子View的measureChildWithMargins() 对子View进行测量
//第四个参数表示当前已使用的宽度 因为是竖直模式 所以为0
//最后一个参数表示已使用的高度 如果之前的子View或者当前的View有weight属性 则当前子视图使用 LinearLayout 的所有高度 已使用的高度为0
measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
//测量完成后 重置子View高度
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
// 比较child测量前后总高度 取较大值
///getNextLocationOffset() 返回0 便于扩展
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
// 设置最高子视图大小
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
// mBaselineChildTop 表示指定的 baseline 的子视图的顶部高度
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
// 设置为 baseline 的子视图的前面不允许设置 weiget 属性
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}
// 宽度测量相关
boolean matchWidthLocally = false;
//当LinearLayout非EXACTLY模式 并且自View为MATCH_PARENT时
//设置matchWidth和matchWidthLocally为true
//该子View占据LinearLayout水平方向上所有空间
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
//对一堆变量赋值
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
二次测量mTotalLength
//根据hasDividerBeforeChildAt得到showDivider的值是否为end 来判断是否需要加上divider的高度
if (mTotalLength > 0 && hasDividerBeforeChildAt(count))
mTotalLength += mDividerHeight;
}
//如果高度测量模式为AT_MOST或者UNSPECIFIED 则进行二次测量 且设置了measureWithLargestChild
if (useLargestChild && (heightMode == MeasureSpec.AT_MOST ||
heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// 计算所有子View的高度之和
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
就是需要useLargestChild
而 mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_measureWithLargestChild, false);
就是说仅在LinearLayout的measureWithLargestChild属性设置为True时(默认为false)才可能出现某个child被二次测量
实例如下
mTotalLength的二次测量二次测量部分View和对为测量的子View进行测量
//加上padding的值
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
//minHeight和当前使用的高度比较取较大值
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
//根据heightMeasureSpec协助计算heightSizeAndState的大小
//resolveSizeAndState方法之后会分析
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
//delta为额外的空间 及LinearLayout中未被分配的空间(可以为负)
int delta = heightSize - mTotalLength;
if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
//skippedMeasure为第一次测量下对跳过测量的子View设置的
//weightSum为权重和 如果设置了总权重则使用我们所设置的 如果没有则使用子View的weight和
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
//测量什么的
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
// Child said it could absorb extra space -- give him his share
//计算weight属性分配的大小
int share = (int) (childExtra * delta / weightSum);
//权重和减去已经分配权重
weightSum -= childExtra;
//剩余高度减去分配的高度
delta -= share;
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);
// TODO: Use a field like lp.isMeasured to figure out if this
// child has been previously measured
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
//子视图已经被测量过
//非EXACTLY view需要加上share
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
}
//重新测量View
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
} else {
//如果当前是EXACTLY模式 说明没有被测量 需要进行测量
//子视图首次被测量
//EXACTLY模式下 将weight占比的高度分配给子View
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));
}
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
//处理子视图宽度
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
// We have no limit, so make all weighted views as tall as the largest child.
// Children will have already been measured once.
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
//使用最大子视图高度测量
child.measure(
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight,
MeasureSpec.EXACTLY));
}
}
}
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
resolveSizeAndState方法 定义在View中
/**
* 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.
* @param childMeasuredState Size information bit mask for the view's
* children.
* @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) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
delta为负的相关解析
相关代码及效果如下
负delta相关解析根据之前的measure流程分析一下
- 相关变量初始化
- 第一次测量 两个子TextView都会被测量 TextView1.height = TextView1.height = 500dp 则mToatalLength为1000dp
- mToatalLength再次测量跳过
- 计算delta delta = heightSize - mTotalLength 根据resolveSizeAndState方法 父LinearLayout是EXACTLY模式 所以最终heightSize为500dp delta = -500dp
- 根据weight分配剩余空间 TextView1.height = 500 + 1 / 5 * (- 500) = 400 dp
TextView2.height = 500 + 4 / 5 * (- 500) = 100 dp
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);
}
}
我们可以看出 同样是分成水平和竖直两个方向的 同样分析竖直 方向下的layout过程
/**
* Position the children during a layout pass if the orientation of this
* LinearLayout is set to {@link #VERTICAL}.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onLayout(boolean, int, int, int, int)
* @param left
* @param top
* @param right
* @param bottom
*/
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
//父View默认子View的宽度
final int width = right - left;
//子View的右侧默认位置
int childRight = width - mPaddingRight;
// 子View的可用空间大小
int childSpace = width - paddingLeft - mPaddingRight;
//子View的个数
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//根据LinearLayout设置的对其方式 设置第一个子View的Top值
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
//遍历各个子View
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//LinearLayout中子View的宽和高有measure过程决定
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//获取子View的LayoutParams
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);
//根据子View的对其方式设置Left值
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;
}
//子View的top修改
childTop += lp.topMargin;
//用setChildFrame()方法设置子控件控件的在父控件上的坐标轴
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
draw 源码分析
protected void onDraw(Canvas canvas) {
if (mDivider == null) {
return;
}
if (mOrientation == VERTICAL) {
drawDividersVertical(canvas);
} else {
drawDividersHorizontal(canvas);
}
}
同样主要分析垂直方向的处理
void drawDividersVertical(Canvas canvas) {
final int count = getVirtualChildCount();
//根据计算好的坐标绘制对应的子View
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int top = child.getTop() - lp.topMargin - mDividerHeight;
drawHorizontalDivider(canvas, top);
}
}
}
//绘制分割线
if (hasDividerBeforeChildAt(count)) {
final View child = getLastNonGoneChild();
int bottom = 0;
if (child == null) {
bottom = getHeight() - getPaddingBottom() - mDividerHeight;
} else {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
bottom = child.getBottom() + lp.bottomMargin;
}
drawHorizontalDivider(canvas, bottom);
}
}
void drawHorizontalDivider(Canvas canvas, int top) {
mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
mDivider.draw(canvas);
}
以上
【附录】
需要资料的朋友可以加入Android架构交流QQ群聊:513088520
点击链接加入群聊【Android移动架构总群】:加入群聊
获取免费学习视频,学习大纲另外还有像高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)等Android高阶开发资料免费分享。
网友评论