Android文本过长时根据关键字省略内容Ellipsize

2,376 阅读2分钟

有时候我们需要根据关键字截断文本内容,省略多余部分,比如微信搜索聊天记录时会在关键字的前后进行截断.

处理方式和逻辑见如下代码和注释:

private static void ellipsizeByKeyword(final TextView textView, String content, String keyword, boolean ignoreCase) {
        TextPaint paint = textView.getPaint();
        String compareContent = ignoreCase ? content.toLowerCase(Locale.ENGLISH) : content;
        String compareKeyword = ignoreCase ? keyword.toLowerCase(Locale.ENGLISH) : keyword;

        final int keywordStart = compareContent.indexOf(compareKeyword);
        if (keywordStart < 0) { // 找不到关键字
            textView.setText(null);
            return;
        }

        int maxLine = TextViewCompat.getMaxLines(textView);
        if (maxLine <= 0) { // 没有行数限制
            textView.setText(content);
            return;
        }
        // 每行文字的最大显示宽度
        int availableWidth = textView.getWidth() - textView.getPaddingLeft() - textView.getPaddingRight();
        // 区分单行和多行做不同的处理
        if (maxLine < 2) { // 单行
            int availableCount = 0; // 一行可显示的字符数
            //&emsp;如果关键字可在截断尾部后的内容中找到,则直接截断尾部
            String newCharSeq = TextUtils.ellipsize(compareContent, paint, availableWidth, TextUtils.TruncateAt.END).toString();
            availableCount = newCharSeq.length();
            if (newCharSeq.contains(compareKeyword)) {
                textView.setEllipsize(TextUtils.TruncateAt.END);
                textView.setText(content);
                return;
            }

            //&emsp;如果关键字可在截断首部后的内容中找到,则直接截断首部
            newCharSeq = TextUtils.ellipsize(compareContent, paint, availableWidth, TextUtils.TruncateAt.START).toString();
            availableCount = Math.max(newCharSeq.length(), availableCount);
            if (newCharSeq.contains(compareKeyword)) {
                textView.setEllipsize(TextUtils.TruncateAt.START);
                textView.setText(content);
                return;
            }

            // 关键字太长了,一行不够显示
            if (availableCount <= keyword.length()) {
                // display: ELLIPSIS_NORMAL + keyword + ...
                textView.setEllipsize(TextUtils.TruncateAt.END);
                textView.setText(ELLIPSIS_NORMAL + keyword);
                return;
            }

            // 关键字在内容中间位置,则首尾都要加上省略号
            // display: ELLIPSIS_NORMAL + xxx + keyword + xxx +...
            textView.setEllipsize(TextUtils.TruncateAt.END);
            int start = keywordStart - (availableCount - keyword.length()) / 2;

            String text = ELLIPSIS_NORMAL + content.substring(start >= 0 ? start : 0);
            newCharSeq = TextUtils.ellipsize(text, paint, availableWidth, TextUtils.TruncateAt.END).toString();
            if (newCharSeq.contains(compareKeyword)) {
                textView.setText(text);
            } else {
                textView.setText(ELLIPSIS_NORMAL + content.substring(keywordStart));
            }
        } else { // multi line
            List<Point> linesStart = getLineStartAndEnd(textView.getPaint(), compareContent, availableWidth);
            int keywordLineStart = getKeywordLine(keywordStart, linesStart); // 在原始内容中,关键字第一个字符的行位置
            int keywordLineEnd = getKeywordLine(keywordStart + compareKeyword.length() + 1, linesStart); // 在原始内容中,关键字最后一个字符的行位置
            if (keywordLineEnd - keywordLineStart < maxLine) {
                int endLine = Math.min(keywordLineStart + maxLine / 2, linesStart.size() - 1); // linesStart.size() - 1 = lastLines
                int startLine = Math.max(endLine - (maxLine - 1) + maxLine % 2, 0); // if maxline is odd, starline+1
                textView.setEllipsize(TextUtils.TruncateAt.END);
                if (startLine == 0) {
                    // display: xxx + keyword + xxx +...
                    textView.setText(content);
                } else {
                    // display: ELLIPSIS_NORMAL + xxx + keyword + xxx +...
                    int start = linesStart.get(startLine).x;
                    textView.setText(ELLIPSIS_NORMAL + content.substring(start));
                }
            } else { // // 关键字太长了
                // display: ELLIPSIS_NORMAL + keyword + xx ...
                textView.setEllipsize(TextUtils.TruncateAt.END);
                textView.setText(ELLIPSIS_NORMAL + content.substring(keywordStart));
            }
        }
    }
    
    /**
     * 计算每一行的开始字符位置和结束字符位置
     * @return List.size()为总行数.point.x 为当前行的开始字符位置, point.y 为当前行的结束字符位置
     */
    private static List<Point> getLineStartAndEnd(TextPaint tp, CharSequence cs, int lineWidth) {
        // StaticLayout是android中处理文字换行的一个工具类,StaticLayout已经实现了文本绘制换行处理
        StaticLayout layout = new StaticLayout(cs, tp, lineWidth, Layout.Alignment.ALIGN_NORMAL,
                1.0f, 0.0f, true);
        int count = layout.getLineCount();
        List<Point> list = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            list.add(new Point(layout.getLineStart(i), layout.getLineEnd(i)));
        }
        return list;
    }

效果:

github

完整代码封装在github上的Androids项目中的EllipsizeUtils类中.

Androids项目是本人根据平时的项目实践经验,为了提高Android开发效率而写的一个工具SDK;里面提供了一些工具类以及自定义View,可在实际项目开发时直接使用。

谢谢大家支持!