返回介绍

3.2 weight

发布于 2024-12-23 21:06:23 字数 8543 浏览 0 评论 0 收藏 0

3.2.1 weight 的再次测量

在上面的代码中,LinearLayout 做了针对没有 weight 的工作,在这里主要是确定自身的大小,然后再针对 weight 进行第二次测量来确定子控件的大小。

我们接着看下面的代码:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    //...接上面
    // 下面的这一段代码主要是为 useLargestChild 属性服务的,不在本文主要分析范围,略过
    if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
      mTotalLength += mDividerHeight;
    }

    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();
        // Account for negative margins
        final int totalLength = mTotalLength;
        mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
            lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
      }
    }
    
    // Add in our padding
    mTotalLength += mPaddingTop + mPaddingBottom;

    int heightSize = mTotalLength;

    // Check against our minimum height
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
    
    // Reconcile our calculated size with the heightMeasureSpec
    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

}  

上面这里是为 weight 情况做的预处理。

我们略过 useLargestChild 的情况,主要看看 if 处理外的代码。在这里,我没有去掉官方的注释,而是保留了下来。从中我们不难看出 heightSize 做了两次赋值,为何需要做两次赋值。

因为我们的布局除了子控件,还有自己本身的 background,因此这里需要比较当前的子控件的总高度和背景的高度取大值。

接下来就是判定大小,我们都知道测量的 MeasureSpec 实际上是一个 32 位的 int,高两位是测量模式,剩下的就是大小,因此 heightSize = heightSizeAndState & MEASURED_SIZE_MASK; 作用就是用来得到大小的精确值(不含测量模式)

接下来我们看这个方法里面第二占比最大的代码:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
		//...接上面

		//算出剩余空间,假如之前是 skipp 的话,那么几乎可以肯定是有剩余空间(同时有 weight)的
    int delta = heightSize - mTotalLength;
    if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
			// 限定 weight 总和范围,假如我们给过 weighSum 范围,那么子控件的 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) {
          // 全篇最精华的一个地方。。。。拥有 weight 的时候计算方式,ps:执行到这里时,child 依然还没进行自身的 measure
					
					// 公式 = 剩余高度*(子控件的 weight/weightSum),也就是子控件的 weight 占比*剩余高度
          int share = (int) (childExtra * delta / weightSum);
					// weightSum 计余
          weightSum -= childExtra;
					// 剩余高度
          delta -= share;
					
          final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
              mPaddingLeft + mPaddingRight +
                  lp.leftMargin + lp.rightMargin, lp.width);
           
          if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
            int childHeight = child.getMeasuredHeight() + share;
            if (childHeight < 0) {
              childHeight = 0;
            }
            
            child.measure(childWidthMeasureSpec,
                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
          } else {
   
            child.measure(childWidthMeasureSpec,
                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                    MeasureSpec.EXACTLY));
          }


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


      mTotalLength += mPaddingTop + mPaddingBottom;

    } 
		
		// 没有 weight 的情况下,只看 useLargestChild 参数,如果都无相关,那就走 layout 流程了,因此这里忽略
		else {
      alternativeMaxWidth = Math.max(alternativeMaxWidth,
                       weightedMaxWidth);

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

3.2.2 weight 的两种情况

这次我的注释比较少,主要是因为需要有一大段的文字来描述。

在 weight 计算方面,我们可以清晰的看到,weight 为何是针对剩余空间进行分配的原理了。 我们打个比方,假如现在我们的 LinearLayout 的 weightSum=10,总高度 100,有两个子控件(他们的 height=0dp),他们的 weight 分别为 2:8 ​。

那么在测量第一个子控件的时候,可用的剩余高度为 100,第一个子控件的高度则是 100*(2/10)=20 ​,接下来可用的剩余高度为 80

我们继续第二个控件的测量,此时它的高度实质上是 80*(8/8)=80

到目前为止,看起来似乎都是正确的,但关于 weight 我们一直有一个疑问:**就是我们为子控件给定 height=0dp 和 height=match_parent 时我们就会发现我们的子控件的高度比是不同的,前者是 2:8 而后者是调转过来变成 8:2 **

对于这个问题,我们不妨继续看看代码。

接下来我们会看到这么一个分支:

if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {} else {}

首先我们不管 heightMode,也就是父类的测量模式,剩下一个判定条件就是 lp.height,也就是子类的高度。

既然有针对这个进行判定,那就是意味着肯定在此之前对 child 进行过 measure,事实上,在这里我们一早就对这个地方进行过描述,这个方法正是 measureChildBeforeLayout()

还记得我们的 measureChildBeforeLayout() 执行的先行条件吗?YA,just u see,正是不满足(LinearLayout 的测量模式非 EXACTLY/child.height==0/child.weight/child.weight>0)之中的 child.height==0 ​。

因为除非我们指定 height=0,否则 match_parent 是等于-1,wrap_content 是等于-2。

在执行 measureChildBeforeLayout() ,由于我们的 child 的 height=match_parent,因此此时可用空间实质上是整个 LinearLayout,执行了 measureChildBeforeLayout() 后,此时的 mTotalLength 是整个 LinearLayout 的大小

回到我们的例子,假设我们的 LinearLayout 高度为 100,两个 child 的高度都是 match_parent,那么执行了 measureChildBeforeLayout() 后,我们两个子控件的高度都将会是这样:

child_1.height=100
child_2.height=100
mTotalLength=100+100=200

在一系列的 for 之后,执行到我们剩余空间:

int delta = heightSize - mTotalLength;
(delta=100[linearlayout 的实际高度]-200=-100)

没错,你看到的的确是一个负数。

接下来就是套用 weight 的计算公式:

share=(int) (childExtra * delta / weightSum)

即: share = -100(2/10) = -20;

然后走到我们所说的 if/else 里面

 if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
            // child was measured once already above...
            // base new measurement on stored values
            int childHeight = child.getMeasuredHeight() + share;
            if (childHeight < 0) {
              childHeight = 0;
            }
            
            child.measure(childWidthMeasureSpec,
                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
          } 

我们知道 child.getMeasuredHeight()=100 ,接着这里有一条 int childHeight = child.getMeasuredHeight() + share; ,这意味着我们的 childHeight=100+(-20)=80;

接下来就是走 child.measure,并把 childHeight 传进去,因此最终反馈到界面上,我们就会发现,在两个 match_parent 的子控件中,weight 的比是反转的。

接下来没什么分析的,剩下的就是走 layout 流程了,对于 layout 方面,要讲的其实没什么东西,毕竟基本都是模板化的写法了。


如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文