LinearLayout 绘制流程解析详解手机开发

太久没有好好学习Android的基础知识了,趁机好好静下心来学习、输出。

LinearLayout绘制流程解析

RelativeLayout绘制流程解析

ListView绘制流程解析

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); 
    } 
} 

根据方向分成垂直和水平测量流程,这里仅讨论垂直方向

测量流程

这才正式开始了真正的垂直方向的测量流程:

  1. 首次测量,遍历测量子View,除了设置weight无法测量的子View;
  2. 计算更新最大的总高度;
  3. 若子View需要重新分配,则重新测量
  4. 计算测量更新最大的宽度
  5. 设置布局宽高

垂直测量

// 垂直测量 
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)); 
} 
} 

宽度的测量策略:

  1. LinearLayout的宽为EXACTLY,则为定值
  2. 子View的宽都为match_parent , 则LinearLayout 的宽度由子View最大的宽度决定
  3. 子View的宽不都为 match_parent ,则因为LinearLayout的宽度也没决定,所以子View指定为match_parent也就无效了,结果LinearLayout 宽度由非 match_parent 的子视图宽度决定,

总结

设置了weight权重的子View会导致更多的测量工作,为了提升效率,尽量减少设置weight

参考

云图网

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6254.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论