This page looks best with JavaScript enabled

单词高亮TextView

 ·  ☕ 3 min read

单词高亮的TextView控件。额,为什么做这个…. 好吧,之前面试时公司要求的题目

  • 特定词组高亮显示(中文/英文)
  • 单词自动换行
  • 高亮词组保持在同一行显示

继承自 View 实现,文本都是使用画布画上去。使用两支画笔表示默认文本和高亮文本。

文本分组实现

ExtendText 表示文本单元是否高亮

1
2
3
4
5
6
7
8
private class ExtendText {
    String textUnit;
    boolean isHighlight;
    ExtendText(String textUnit, boolean isHighlight) {
        this.textUnit = textUnit;
        this.isHighlight = isHighlight;
    }
}
  1. 将原文中匹配给定的高亮词组的前后加 # 号
  2. 根据 # 号将原文 split 成数组
  3. 遍历数组,构造 ExtendText 数组
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void setDisplayedText(final String text, final List<String> highlighted) {
    if (TextUtils.isEmpty(text)) {
        return;
    }
    String s = text;
    String[] words;
    extendTexts.clear();
    if (highlighted != null) {
        for (String str : highlighted) {
            s = s.replaceAll(str, "#" + str + "#");
        }
        words = s.split("#");
        for (String str : words) {
            boolean isHighlight = highlighted.contains(str);
            if (isHighlight) {
                ExtendText t = new ExtendText(str, true);
                extendTexts.add(t);
            } else {
                for (String word : Arrays.asList(str.split(" "))) {
                    ExtendText tt = new ExtendText(word + " ", false);
                    extendTexts.add(tt);
                }
            }
        }
    } else {
        words = s.split(" ");
        for (String str : words) {
            ExtendText t = new ExtendText(str, false);
            extendTexts.add(t);
        }
    }
    requestLayout();
    invalidate();
}

控件不同情况下尺寸确定

在这里也学习了自定义 View 里的尺寸测量方法

View 的测量模式有3种:

  • UNSPECIFIED: 表示视图的尺寸未指明,比如 wrap_content 模式下,如果此时父容器也是wrap_content(比如父容器的 ScrollView),则需要自己计算 View 的实际占用值
  • AT_MOST: 表示视图的尺寸最多达到多少,比如 match_content,一般取测量值和View实际占用值的最小值
  • EXACTLY: 表示视图的尺寸是确定的,比如 layout_width="100dp",一般直接返回测量值

首先是 onMeasure 里根据测量值和测量模式获取实际需要绘制的宽高

1
2
3
4
5
6
7
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    width = measureWithSize(MeasureSpec.getSize(widthMeasureSpec), widthMeasureSpec);
    width -= (getPaddingRight() + getPaddingLeft());
    int height = measureHeightSize(heightMeasureSpec);
    setMeasuredDimension(width, height);
}

测量宽度

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private int measureWithSize(int defaultSize, int measureSpec) {
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
        case MeasureSpec.AT_MOST:
            return Math.min(defaultSize, specSize);
        case MeasureSpec.EXACTLY:
            return specSize;
    }
    return defaultSize;
}

测量高度

高度的测量比宽度多了UNSPECIFIED模式下,自己测量了View需要的高度

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private int measureHeightSize(int measureSpec) {
    int result;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    //确保layout_height为wrap_content时所占高度合适
    int wrapHeight = (int) (getPaddingTop() + dfPaint.getTextSize() + getPaddingBottom());
    float x_draw = getPaddingLeft();
    for (ExtendText t : extendTexts) {
        Paint paint = t.isHighlight ? hlPaint : dfPaint;
        float textLen = paint.measureText(t.textUnit);
        if (x_draw + textLen > width) {
            x_draw = getPaddingLeft();
            wrapHeight += paint.getTextSize();
        }
        x_draw += textLen;
    }
    result = wrapHeight;
    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            //父控件为Scrollview情况,直接使用wrapHeight
            break;
        case MeasureSpec.AT_MOST:
            result = Math.min(result, specSize);
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
    }
    return result;
}

绘制时自动换行

使用 Pain.measureText测量画笔绘制文本将要的宽度,然后与空间的宽度比较判断是否需要换行,换行就增加 y 方向的坐标值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    float x_draw = getPaddingLeft();
    float y_draw = getPaddingTop() + dfPaint.getTextSize();
    for (ExtendText t : extendTexts) {
        Paint paint = t.isHighlight ? hlPaint : dfPaint;
        float textLen = paint.measureText(t.textUnit);
        if (x_draw + textLen > width) {
            x_draw = getPaddingLeft();
            y_draw += paint.getTextSize();
        }
        canvas.drawText(t.textUnit, x_draw, y_draw, paint);
        x_draw += textLen;
    }
}
Support the author with
alipay QR Code
wechat QR Code

Yang
WRITTEN BY
Yang
Developer