• android--TextView在刷新时宽度变大的问题排查记录


    android–TextView在刷新时宽度变大的问题排查记录

    问题复现

    • 在api30及以下能必现
    • 往textview设置字符串“2\n2\n2\n2”,打开布局边界开关后效果如下
    • 在这里插入图片描述
    • 在触发拉起输入法等会导致textview刷新的操作后,你会发现textview变宽了一些
    • 在这里插入图片描述
    • 而这增加的宽度就是换行符的宽度

    问题排查

    • 跟踪时发现TextView的onMeasure会走入不同的条件,进而产生不同的宽度
    //以api30为例
     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         		 ...
           
                if (mLayout != null && mEllipsize == null) {
                	// 第二次测量会走这里
                    des = desired(mLayout);
                }
    
              	...
    
                if (boring == null || boring == UNKNOWN_BORING) {
                    if (des < 0) {
                    	// 第一次测量会走这里
                        des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
                                mTransformed.length(), mTextPaint, mTextDir, widthLimit));
                    }
                    width = des;
                } else {
                    width = boring.width;
                }
                ...
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 上面代码注释的地方就是两个关键点,因为这里计算出来的des是不同的,导致画面上会有宽度变大的情况出现
    • 再仔细跟踪desired和Layout.getDesiredWidthWithLimit方法,发现desired里在计算宽度时会把行末的换行符宽度计算进去,而Layout.getDesiredWidthWithLimit则不会
    • 那再看一下desired方法
    //以api30为例
    private static int desired(Layout layout) {
            int n = layout.getLineCount();
            CharSequence text = layout.getText();
            float max = 0;
    
            // if any line was wrapped, we can't use it.
            // but it's ok for the last line not to have a newline
    
            for (int i = 0; i < n - 1; i++) {
                if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
                    return -1;
                }
            }
    
            for (int i = 0; i < n; i++) {
            	//关键就是这一句
                max = Math.max(max, layout.getLineWidth(i));
            }
    
            return (int) Math.ceil(max);
        }
       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 发现计算宽度实际是layout.getLineWidth(i)这一行,那就再看看getLineWidth方法
      public float getLineWidth(int line) {
            float margin = getParagraphLeadingMargin(line);
            //关键就是这一句
            float signedExtent = getLineExtent(line, true);
            return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 关键来了,就是getLineExtent的第二个参数传了true,才导致把换行符的宽度都算进去了
    • google在api31的时候修复了这个问题
    //api31
     private static int desired(Layout layout) {
            int n = layout.getLineCount();
            CharSequence text = layout.getText();
            float max = 0;
    
            // if any line was wrapped, we can't use it.
            // but it's ok for the last line not to have a newline
    
            for (int i = 0; i < n - 1; i++) {
                if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
                    return -1;
                }
            }
    
            for (int i = 0; i < n; i++) {
            	//关键就是这一句
                max = Math.max(max, layout.getLineMax(i));
            }
    
            return (int) Math.ceil(max);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 就是把layout.getLineWidth(i)换成了layout.getLineMax(i)
    • 而getLineMax里其实就改变了getLineExtent的第二个参数为false而已
    //api31
     public float getLineMax(int line) {
            float margin = getParagraphLeadingMargin(line);
            //关键就是这一句
            float signedExtent = getLineExtent(line, false);
            return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    不能称之为解决办法的办法

    • 那么现在问题来了,如何解决api31以下的手机呢
    • TextView是系统的,无法通过修改字节码的方式来修改,那么就只有想方设法规避进入desired那个方法
    			if (mLayout != null && mEllipsize == null) {
                	// 第二次测量会走这里
                    des = desired(mLayout);
                }
    
    • 1
    • 2
    • 3
    • 4
    • 那么就只剩mLayout为null或mEllipsize不为null了
    • 要使mLayout为空,可能得进行反射处理,但mLayout为null会不会导致测量出现问题,这很难说
    • 要使mEllipsize不为空,那么就xml设置Ellipsize或代码设置即可
    • 可是问题又来了,本来以为TextView没有通过maxLines来限制行数,即使加上android:ellipsize="end"也不会出现省略号吧,可是诡异的事情就是这么诡异,某天竟然有人反馈说TextView中出现了省略号,而且还不是必现的,关键是我设置的是end,即使出现省略号,也应该在末尾,可这省略号竟然出现在中间,无语了(不会又触发了系统bug吧)
    • 所以到这里也没有一个完美的解决办法,期待有人告知

    参考

  • 相关阅读:
    Linux环境基础开发工具使用(上)
    fltp备份文件后统计验证
    【901. 股票价格跨度】
    多级缓存与局部性原理
    Ubuntu 安装 npm 和 node
    01 MySQL优化 - 优化SQL语句
    纯分享:将MySql的建表DDL转为PostgreSql的DDL
    js中的this举例介绍
    Python zip函数及用法
    maxwell源码编译安装部署
  • 原文地址:https://blog.csdn.net/huweijian5/article/details/126749676