TextView应该是Android开发中使用频次非常高的一个基础控件。对于长文本,TextView默认的处理方案是换行显示,对于只需要单行显示的TextView加上android:singleLine="true"即可让TextView单行显示,同时如果文本超过一行自动加上省略号,但是如果UI是类似这种呢?再只使用一个控件的情况下,实现的这样的显示目前能想到的有一下几种。

QQ20180922-180934@2x.png

计算文字行数

第一种方法思路很简单,要实现这种效果只需要在还没有进行settext方法之前就先判断一下文本是否超长,如果超过一行的长度就把第一行取出来,进行手动拼接后(第一行文本+…等X人)设置到TextView

计算文本是否有多行

TextView自身就限制行数的API所以,判断文本有几行,这代码TextView肯定已经有了,我们打开TextView的源代码,果不其然

···
 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
                text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));

        layoutBuilder.setAlignment(getLayoutAlignment())
                .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                .setIncludePad(getIncludeFontPadding())
                .setBreakStrategy(getBreakStrategy())
                .setHyphenationFrequency(getHyphenationFrequency())
                .setJustificationMode(getJustificationMode())
                .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                .setTextDirection(getTextDirectionHeuristic());

        final StaticLayout layout = layoutBuilder.build();

        // Lines overflow.
        if (maxLines != -1 && layout.getLineCount() > maxLines) {
            return false;
        }
···

layout.getLineCount()可以看到 StaticLayout(StaticLayout用法)这个类来判断文本行数的,我们整理一下代码,结合需求变成 (之所以是StaticLayout.Builder而不直接是StaticLayout是因为在API28中,StaticLayout已经被标记为deprecated,而替代它的正是StaticLayout.Builder)

  /**
     * 判断是否有多行 文字处理
     */
    private boolean setLastIndexForLimit() {
        //实例化StaticLayout 传入相应参数
        StaticLayout staticLayout = new StaticLayout(getText(), getPaint(),
                getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
        return staticLayout.getLineCount() > 1;
    }

下面在介绍一种判断文本是否有多行的方法(感觉不是很准),通过textview.getPaint().measureText(String text)方法就可以获取渲染出来的文本的长度了。通过比较TextView的宽度和渲染出来的文本宽度就可以判断是否超过字符串长度了。

现在已经判断了文本是否有多行了,下一步就是取出第一行了

取出第一行

结合StaticLayout可以通过取出第一行

   //取出第一行
        String firstLineText =  getText().toString().substring(0,staticLayout.getLineEnd(0));
        firstLineText =  firstLineText .substring(0,firstLineText.length() - endText.length());

使用measureText方法可以通过不断测量文字看度的方法获取来判断

//测量出最后要加载结尾的字符的长度
val mViewWidth = textview.getMeasuredWidth()
float textWidth = getPaint().measureText(text);
 float ellipsisWidth = getPaint().measureText(“...等X人”);
            float authenticTextWidth = mViewWidth - ellipsisWidth;
            for (int i = 0; i < text.length(); i++) {
                if (getPaint().measureText(text.substring(0, i + 1)) > authenticTextWidth) {
                    text = text.substring(0, i) + endText;
                    break;
                }
            }

以上两种方法都可以取出第一行需要渲染的字符串,两种方式各有优缺点,但是共同的问题是渲染出来的实际效果并不能跟TextView的长度匹配,有时候会出现明明TextView还有空间,但是已经显示…的情况(原因可能是因为英文、数字跟汉字占得宽度不同的问题)

如果要更方便的使用继承TextView,将以上方法封装到里面即可

将结尾文本渲染成图片

这种方法无疑是更方便简单,同时性能和效果上更好,虽然代码实现上没有什么复杂的代码,但是思路很新颖,我们做程序最重要的是解决问题的思路,很多时候我们可能换个思路困难就会迎刃而解

简单看看实现代码:

  String sizeLength = ...等X人;
            TextDrawable count = new TextDrawable(sizeLength, SizeUtils.sp2px(16), context.getResources().getColor(R.color.title), Color.TRANSPARENT, 0, 0);
            textView.setCompoundDrawables(null, null, count, null);
textView.setText(text)