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/tech/app/6254.html

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

相关推荐

发表回复

登录后才能评论