单词高亮的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); 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: 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; } }