美文网首页工作生活
LinearLayout的weight属性引发深思

LinearLayout的weight属性引发深思

作者: Magic旭 | 来源:发表于2019-07-04 17:40 被阅读0次

    需求场景

    1. 一行中有3个TextView处于左边,以此排序。单独有一个TextView处于屏幕的最右边。然后左边优先级最高,最大展示一行,超过一行显示三点和把自身右边的TextView挤出屏幕外。

    自身想法

    1. 创建一个LinearLayout父布局,把前3个TextView(t1,t2,t3)放进LinearLayout里面,然后加一个View(View:layout_width = 0dp,layout_weight = 1),然后再到TextView(t4)。在布局层面上确实可以达到效果,t4处于屏幕的最右边。t1,t2,t3不管显示和隐藏,t4都能处于屏幕最右边。

    2. 效果如下图


      image.png

    问题

    1. 上面虽然把整体布局实现了,但是问题来了,前面三个TextView超长的时候都能限制在LinearLayout的大小内,但是最右边的TextView超长后直接比LinearLayout的宽度还要大,宽度会延伸到屏幕的最右边。
      效果图


      image.png
    解决问题
    1. 这里用LinearLayout的Horizontal方向为例子讲解。首先LinearLayout的onMeasure方法里会用for循环逐个取出childView,每一次取出的childView都会取其LayoutParams,目的是为了计算整体的weight值大小。
    for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                ……
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                totalWeight += lp.weight;
                final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;
                if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
                        //这个if就是对应自我深思第三点的第三中方法,width = 0,weight > 0
                      ……
                }else{
                    ……
                     //记录下前面childView已用的宽度(如果是垂直方法,应该是记录已用的高度)
                     //1.如果当前的totalWeight为0,用parentWidth - 已用宽度 = 剩余宽度,把剩余宽度告知子View
                     //2.如果当前的totalWeight不为0,直接用parentWidth宽度告知子View
                     final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
                     measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
                            heightMeasureSpec, 0);
               
                    final int childWidth = child.getMeasuredWidth();
                }
    }
    
    1. measureChildBeforeLayout方法
      measureChildBeforeLayout是根据输入的size和mode,最后拼成一对childWidthMeasureSpec和childHeightMeasureSpec,重新塞给childView去计算宽高。
    //最终走到这里
    /***
    *** parentWidthMeasureSpec和parentHeightMeasureSpec就是父size和父mode的 | 操作,不懂自行再去查询。
    *** widthUsed: 已用宽度 heightUsed:已用高度
    ***/
    protected void measureChildWithMargins(View child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                            + widthUsed, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                            + heightUsed, lp.height);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    
    
    1. 从上面分析可以很清楚看到为什么我预期和结果不一致了。LinearLayout只要遇到子View设置了layout_weight属性,就会把前面占用的大小清0,后面的View就会根据自身测量大小,LinearLayout不做限制,要多大给多大。因为我的TextView都是wrap_content,其大小是字体Text的大小;所以就会导致如果字体很长,我的最右边的TextView就会无限延长,超出了LinearLayout自身的大小了。这就是根本原因,代码原因如下
    //只要有子View设置了layout_weight属性,从该子View起,后面的子View都得不到已经占用的大小信息,因为强制设置成了0.
     final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
    

    自我深思

    1. 对layout_weight内部功能如何实现不够清晰,只知道可以用于占满剩余的空间。
    2. 遇到问题多debug看看源码,其实看了源码后你会觉得自己基础知识都能巩固了不少。
    3. 如果要解决这个问题:一、最右边TextView手动限制大小,maxEms或者width = “xxxdp”。二、换一种父布局实现效果。三、去除里面的View,将最右边的TextView设置成width = 0dp,layout_weight = 1,gravity = right,也能实现对应效果。

    相关文章

      网友评论

        本文标题:LinearLayout的weight属性引发深思

        本文链接:https://www.haomeiwen.com/subject/mtvrhctx.html