太久没有好好学习Android的基础知识了,趁机好好静下心来学习、输出。
RelativeLayout绘制流程解析
RecyclerView绘制流程解析
前言
之前《View/ViewGroup 绘制流程和疑惑》写了关于ViewGroup的绘制流程,在此基础上,继续分析LinearLayout的绘制流程,其实无论时LinearLayout还是RelativeLayout,都是继承自ViewGroup,除了Measure流程重写之外,layout和draw基本延续了VIewGroup的一套。
从LinearLayout的绘制,也就是测量开始:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
// 垂直方向
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
// 水平方向
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
根据方向分成垂直和水平测量流程,这里仅讨论垂直方向
测量流程
这才正式开始了真正的垂直方向的测量流程:
- 首次测量,遍历测量子View,除了设置weight无法测量的子View;
- 计算更新最大的总高度;
- 若子View需要重新分配,则重新测量
- 计算测量更新最大的宽度
- 设置布局宽高
垂直测量
// 垂直测量
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0; // 总高度,子控件累加总高度
int maxWidth = 0; // 最大子控件的宽度
int childState = 0; // 子view测量状态
int alternativeMaxWidth = 0; // 没有设置weight的子view的最大宽度
int weightedMaxWidth = 0; // 设置weight的子view的最大宽度
boolean allFillParent = true; // 子控件全设置match_parent
float totalWeight = 0; // 子控件累加总权重(子控件设置了layout_weight)
final int count = getVirtualChildCount(); // 子控件总数量
// 测量模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false; // 有子View宽度设置为match_parent
// 是否跳过重新测量
boolean skippedMeasure = false;
// 基线子view
final int baselineChildIndex = mBaselineAlignedChildIndex;
// 跟setMeasureWithLargestChildEnabled()有关
// 当设定为true,所有有设定了weight的子View的最小高度是:最大的View的高度
final boolean useLargestChild = mUseLargestChild;
// 最大子控件的高度
int largestChildHeight = Integer.MIN_VALUE;
int consumedExcessSpace = 0;
// 需要测量的子View总数,不需要测量指的是设定了weight
int nonSkippedChildCount = 0;
// 首次测量,测量能测量到的子View的高度和最大宽度,不包括weight的子View
for (int i = 0; i < count; ++i) {
/**** 备注:第一次测量,见分析一 ****/
}
// 底部是否有分割线,计算最高高度
if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
if (useLargestChild && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
//设置了 measureWithLargestChild 且 总高度无法确定,需要重新计算 mToatalLength
for (int i = 0; i < count; ++i) {
/**** 备注:见分析二 ****/
}
}
// 添加padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// 检查最小高度
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// 根据heightMeasureSpec计算最后结果,heightSizeAndState存储最终的结果
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
int remainingExcess = heightSize - mTotalLengt + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
// 根据上次的测量结果,重新测量子View
if (skippedMeasure || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
// 计算总的weight,mWidgetSum是外部设定,totalWeight是首次计算出来的
float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
/**** 备注:重新测量,见分析三 *****/
}
mTotalLength += mPaddingTop + mPaddingBottom;
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth);
// 如果设定了useLargestChild
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
for (int i = 0; i < count; i++) {
/**** 备注:见分析四 ****/
}
}
}
// 修正宽度
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// 检查最小高度
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);
...
}
流程分析一
// 首次测量
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
// 测量view为null占据的高度,默认返回0
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
// 跳过
i += getChildrenSkipCount(child, i);
continue;
}
// 需要测量 + 1
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 增加权重
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
// 子View的高度是具体的,可以这直接算出来,不需算子View的高度
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true; // 该子View直接忽略二次计算
} else {
// 无法精确计算子View
if (useExcessSpace) {
lp.height = LayoutParams.WRAP_CONTENT;
}
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
// 内部也是调用了measureChildWithMargin()
measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
// 设置了权重
lp.height = 0;
consumedExcessSpace += childHeight;
}
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
// 设置了setMeasureWithLargestChildEnabled
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
...
// 计算最大宽度
boolean matchWidthLocally = false;
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);
}
首次计算都计算了每个View,包括设置了权重的view。而且只是尽可能地算出最大的总高度,并计算出了子控件中的最大宽度。
如果子view的 height 为 0,weight 不为 0,说明该view希望使用 LinearLayout 的剩余空间,则不需要测量。
如果LinearLayout 的高度测量规格为 Exactly ,说明 LinearLayout 的高度已经确定,不依赖子视图的高度计算自己的高度,同样不需要测量子view。
流程分析二
此次测量目的是为了计算mTotalLength,触发的条件是设置了 measureWithLargestChild 且 总高度无法确定,就需要重新计算 mToatalLength
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));
}
流程分析三
这次是在上次测量之后,重新触发的测量。根据 weight 分配剩余高度。
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final float childWeight = lp.weight;
if (childWeight > 0) {
// 计算分配的值
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
// 剩余分配高度
remainingExcess -= share;
// 剩余比重总和
remainingWeightSum -= childWeight;
final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
// 子View直接是largestChildHeight
childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
|| heightMode == MeasureSpec.EXACTLY)) {
// 子view是没有height,直接为分配高度
childHeight = share;
} else {
// 子View有height,另外还要加上分配的高度
childHeight = child.getMeasuredHeight() + share;
}
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
// 重新测量子View
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
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;
// 计算子View的最大宽度
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));
}
分析三的测量目的是根据weight分配到每个View的具体高度,有可能分配到的height是负值。
流程分析四
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));
}
}
宽度的测量策略:
- LinearLayout的宽为EXACTLY,则为定值
- 子View的宽都为match_parent , 则LinearLayout 的宽度由子View最大的宽度决定
- 子View的宽不都为 match_parent ,则因为LinearLayout的宽度也没决定,所以子View指定为match_parent也就无效了,结果LinearLayout 宽度由非 match_parent 的子视图宽度决定,
总结
设置了weight权重的子View会导致更多的测量工作,为了提升效率,尽量减少设置weight
参考
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6254.html