单词高亮的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;
}
}
|
- 将原文中匹配给定的高亮词组的前后加 # 号
- 根据 # 号将原文
split
成数组
- 遍历数组,构造
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;
}
}
|