之前有个项目需要对文字实现局部点击;实现时用到了Span,感觉很有意思,所以记录一下。
Span主要用于TextView中内容的显示,可以控制内容的大小,样式,颜色,文字风格,文字的字形等。功能十分的强大。
Span只是Android开发中的一个概念,并不是一个类,或者是一个接口;因为TextView可以显示文字,图片;显示文字时也有多有少,有时可能要控制某一个段落的文字的样式;也有时要控制某几个字符的文本格式或者是修改文本的外观;甚至有时还要对文本的大小等进行修改;所以Span也要分上几大类:
- 修改字符文本格式时 使用
CharacterStyle
- 修改字符外观时 使用
UpdateAppearance
- 修改文字段落格式时 使用
ParagraphStyle
- 修改文字大小度量时 使用
UpdateLayout
下面是从AndroidStudio截取的上面这四个类或者接口的子类图:
CharacterStyle
的子类图:
根据字面意思,我们可以看到,里面有 控制背景色的Span,控制点击的Span,控制前景色的Span,给文字添加马赛克过滤功能的Span,给文字添加删除线的Span,给文字添加下划线的Span等。
UpdateAppearance
的子类图:
根据字面意思,我们可以看到,里面有的Span跟CharacterStyle里的功能很多一样;但是它有一个子接口 Updatelayout
。
ParagraphStyle
的子接口图:
这个里面有 控制文字对齐的Span,控制文字头部样式的Span等。
UpdateLayout
的子类图:
这个里面有控制文字绝对/相对大小的Span,文字风格的Span,X轴文字缩放的Span等。
上面可能看的比较乱,下面附上几张在flavienlaurent 上获取的 beautiful class diagrams :
当TextView
调用setText()
时,它使用布局的基类Layout
来进行文字的渲染。Layout
类里会在渲染的时候去判断当前的文本是否是一个Spanned
(Spanned是Spannable的父接口,SpannableString是Spannable的子类),如果是Spanned
就会使用Spanned
去负责文本的显示。下面是Layout
的drawText()
的一部分代码:
public void drawText(Canvas canvas, int firstLine, int lastLine) {
// ……
// 省略一大部分代码
//……
ParagraphStyle[] spans = NO_PARA_SPANS;
TextLine tl = TextLine.obtain();
//判断是否是一个Spanned
if (mSpannedText) {
Spanned sp = (Spanned) buf;
int textLength = buf.length();
boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n');
if (start >= spanEnd && (lineNum == firstLine || isFirstParaLine)) {
spanEnd = sp.nextSpanTransition(start, textLength,
ParagraphStyle.class);
//这里只会处理ParagrahSpan
spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
paraAlign = mAlignment;
for (int n = spans.length - 1; n >= 0; n--) {
if (spans[n] instanceof AlignmentSpan) {
paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
break;
}
}
tabStopsIsInitialized = false;
}
}
// ……
// 省略一大部分代码
//……
}
上面代码有个变量 mSpannedText
,它是在构造方法里这样赋值的 mSpannedText = text instanceof Spanned;
;这样判断当前文本是否是一个Spanned
.
在Layout
调用draw()
时会先后调用drawBackground()
和drawText()
;一个进行背景的绘制,一个进行文字的绘制。在Layout
里,还有一个TextLine
类,这个才是实际渲染文字类,它会对文字逐行渲染。
TextLine tl = TextLine.obtain();
// Draw the lines, one at a time.
// The baseline is the top of the following line minus the current line's descent.
for (int i = firstLine; i <= lastLine; i++) {
// ……
// 省略一部分代码
//……
Directions directions = getLineDirections(i);
if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
// XXX: assumes there's nothing additional to be done
canvas.drawText(buf, start, end, x, lbaseline, paint);
} else {
tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
tl.draw(canvas, x, ltop, lbaseline, lbottom);
}
}
TextLine.recycle(tl);
其实在TextLine
类里,包含了三个关于Span的集合。
private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
new SpanSet<CharacterStyle>(CharacterStyle.class);
private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
new SpanSet<ReplacementSpan>(ReplacementSpan.class);
总结:
关于Span
的内容很复杂,上面说的有些乱,关于各个Span
的具体应用,到下一节介绍。