本文已同步发表于我的
微信公众号,搜索代码说即可关注,欢迎与我沟通交流。
一 什么是Span
Span 是功能强大的标记对象,可用于在字符或段落级别设置文本样式。通过将 Span 附加到文本对象,可以以各种方式更改文本,如修改颜色、使文本可单击、缩放文本大小以及以自定义方式绘制文本等。Span 还可以更改 TextPaint 属性,在Canvas上绘制,甚至更改文本布局等。
二 创建并应用Span
TextView 通过 setText(CharSequence text) 设置文本,其中参数text是CharSequence类型,而 CharSequence 是处理 Span 富文本的顶层接口,如下:
CharSequence是处理Span的顶层接口;Spannable接口继承自Spanned,Spannable/Spanned都可以获取Span,此外Spannable还可以设置Span;SpannedString、SpannableString和SpannableStringBuilder都是Android中用于处理富文本的类,三者都直接或间接地实现了Spanned接口;SpannableString、SpannableStringBuilder还实现了Spannable接口。
//Spanned接口
public interface Spanned extends CharSequence {
public <T> T[] getSpans(int start, int end, Class<T> type);
public int getSpanStart(Object tag);
public int getSpanEnd(Object tag);
public int getSpanFlags(Object tag);
public int nextSpanTransition(int start, int limit, Class type);
}
//Spannable接口
public interface Spannable extends Spanned {
public void setSpan(Object what, int start, int end, int flags);
public void removeSpan(Object what);
default void removeSpan(Object what, int flags) {
removeSpan(what);
}
}
可以看到
Spanned接口中只有获取Span信息的能力;Spannable除了Spanned的能力外,新增了setSpan及removeSpan的能力。
SpannedString、SpannableString、SpannableStringBuilder三者之间的区别主要体现在可变性和数据结构上:
| Class | 文本可变 | Span可变 | 数据结构 |
|---|---|---|---|
| SpannedString | N | N | 数组实现 |
| SpannableString | N | Y | 数组实现 |
| SpannableStringBuilder | Y | Y | 区间树 |
使用时三者应该如何选择:
- 如果在创建后不修改文本或
Span,请使用SpannedString; - 如果需要将少量
Span附加到单个文本对象,并且文本本身是只读的,请使用SpannableString; - 如果需要在创建后修改文本,并且需要将
Span附加到文本,请使用SpannableStringBuilder; - 如果需要将大量
Span附加到文本对象,无论文本本身是否是只读的,请使用SpannableStringBuilder。
2.1 setSpan(Object what, int start, int end, int flags)
setSpan() 将 Span 对象设置到文本中,从而修改文本状态,来看下方法中各个参数的含义:
- what : 要应用于文本中的从
start到end之间的Span标记。 - start & end : 表明要应用范围的文本部分,默认是左闭右开区间,如
[start,end)。 - flags: 应用
Span后,如果在Span边界(即在开始索引或结束索引处)内继续插入文本,则Span会自动扩展以包含插入的文本,flags参数决定Span是否同步扩展以包含插入的文本。- Spanned.SPAN_INCLUSIVE_INCLUSIVE 包含两端端点,即左闭右闭区间
[start, end] - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE 不包含两端
start和end所在的端点,即左开右开区间(start, end)。 - Spanned.SPAN_INCLUSIVE_EXCLUSIVE 左闭右开区间,即
[start, end) - Spanned.SPAN_EXCLUSIVE_INCLUSIVE 左开右闭区间,即
(start, end]
- Spanned.SPAN_INCLUSIVE_INCLUSIVE 包含两端端点,即左闭右闭区间
注意:flags 参数是在 setSpan() 之后,继续往文本中插入新文本时才会生效。上面的解释比较抽象,下面直接来看一个示例:
String myString = "01234";
int start = 1;
int end = 3;
int spanFlag = Spannable.SPAN_INCLUSIVE_INCLUSIVE; // this is what is changing
// set the span
SpannableStringBuilder spannableString = new SpannableStringBuilder(myString);
ForegroundColorSpan foregroundSpan = new ForegroundColorSpan(Color.RED);
spannableString.setSpan(foregroundSpan, start, end, spanFlag);
// insert the text after the span has already been set
// (inserting at start index second so that end index doesn't get messed up)
spannableString.insert(end, "x");
spannableString.insert(start, "x");
textView.setText(spannableString);
设置不同得 flags 对应的不同结果:
| flags使用 | 效果图 |
|---|---|
| setSpan()之后不再插入文本(默认) | |
| 以SPAN_INCLUSIVE_INCLUSIVE方式在边界插入文本 | |
| 以SPAN_INCLUSIVE_EXCLUSIVE方式在边界插入文本 | |
| 以SPAN_EXCLUSIVE_INCLUSIVE方式在边界插入文本 | |
| 以SPAN_EXCLUSIVE_EXCLUSIVE方式在边界插入文本 |
示例来自:https://stackoverflow.com/questions/9879233/explain-the-meaning-of-span-flags-like-span-exclusive-exclusive
2.2 Span分类
Android 框架定义了在测量和渲染图形时检查的几个接口和抽象类。这些类具有允许 Span 访问 TextPaint 或者 Canvas 对象的方法。
在 android.text.style 包中提供了20多个 Span,对主要的接口和抽象类进行了子类化。可以通过下面的方式进行分类:
Span是否只改变外观还是同时会改变文本测量/布局(appearance vs metric)Span是在字符级别或是在段落级别影响文本 (character vs paragraph)
2.2.1 影响字符级别的Span
a、影响外观的Span
- ForegroundColorSpan:字体颜色样式,用于改变字体颜色;
- BackgroundColorSpan:背景色样式,可以用来设定文本的背景色;
- UnderlineSpan:下划线样式,给文本添加下划线;
- MaskFilterSpan:应用指定的
MaskFilter到文本,可以实现一些特殊的效果,如模糊(BlurMaskFilter)等。
MaskFilterSpan(MaskFilter filter)设置滤镜Span,参数filter是MaskFilter类型。 BlurMaskFilter是MaskFilter子类,可以应用于Paint的一个滤镜。可以将绘制的图形进行模糊处理,以达到一些特定的视觉效果。
1、radius : 模糊半径。数值越大,模糊的范围越广。
2、style是BlurMaskFilter.Blur枚举,表示模糊的类型,包括以下四种:
— a、NORMAL:普通模糊,应用于整个图像。
— b、SOLID:只绘制模糊的区域,其他区域为透明。
— c、OUTER:外部模糊,只在图像外部模糊。
— d、INNER:内部模糊,只在图像内部模糊。
- StrikethroughSpan:删除线样式,给文本添加删除线效果;
- ClickableSpan:设置点击事件,注意点击相关的必须设置
textView.movementMethod = LinkMovementMethod.getInstance(),否则点击不生效。 - URLSpan:点击文字时进行链接跳转。
b、影响测量的Span
设置 Span 之后的对象会重新测量文本以便于正确的布局和渲染。
- ScaleXSpan:在水平方向上缩放文本;
- RelativeSizeSpan:相对大小样式,相对于原始大小调整文本的大小;
- AbsoluteSizeSpan:绝对大小样式,以指定的像素大小设置文本的大小;
- SubscriptSpan:将文本
baseline的位置向下移动,用于显示下标文本; - SuperscriptSpan:将文本
baseline的位置向上移动,用于显示上标文本; - StyleSpan:字体样式,如通过传入的
Typeface设置正常、粗体、斜体、加粗并倾斜等样式; - TextAppearanceSpan:设置文本字体、大小、样式和颜色等
- ImageSpan:图片样式,用于在文本中插入图片;
- TypefaceSpan:设置不同样式的文本字体
2.2.2 影响段落级别的Span
- LeadingMarginSpan:段落缩进,即为文本添加指定的行首缩进;
- LineBackgroundSpan:改变行的背景色,如果
[start,end)不够一行按一行处理; - LineHeightSpan:改变段落的行高,可用于调整行与行之间的间距。注意,
LineHeightSpan改变的是整个段落的行高,即使它只覆盖段落的一部分。 - DrawableMarginSpan:在文本的行首添加一个带有指定边距的
Drawable; - IconMarginSpan:
IconMarginSpan与DrawableMarginSpan类似,在文本的行首添加一个带有指定边距的Bitmap; - QuoteSpan:
QuoteSpan可以在文本开始的地方添加引用样式(一个垂直的线条); - BulletSpan:给文本添加项目符号(圆点样式);
- AlignmentSpan:文本对齐方式,
AlignmentSpan.Standard是其默认实现,其构造函数AlignmentSpan.Standard(Layout.Alignment align)中的align是Layout.Alignment枚举类型参数,包括三种情况:ALIGN_CENTER居中、ALIGN_NORMAL正常、ALIGN_OPPOSITE相反方向。 - TabStopSpan:段落中第一行的左侧偏移量(与
\t有关系),默认实现为TabStopSpan.Standard。
三 自定义Span
通过自定义 Span 可以实现更多样化的文本样式和交互效果。根据自定义的Span影响文本是在字符级别还是段落级别,以及影响的是布局还是外观,可以参考下表:
| 场景 | 类或接口 |
|---|---|
| 字符级别影响文本 | CharacterStyle |
| 段落级别影响文本 | ParagraphStyle |
| 影响文本外观 | UpdateAppearance |
| 影响文本布局 | UpdateLayout |
不过大部分场景下,我们不用继承到这么深的父类,选择合适的已有Span或者继承 ReplacementSpan 进行扩展是一个不错的选择。
可能需要自定义 Span的一些常见场景:
- 定制文本样式:
Android提供了一些内置的Span类,如ForegroundColorSpan、BackgroundColorSpan和StyleSpan,但有时可能需要更多样化的文本样式,例如自定义字体、阴影效果等。通过自定义Span,可以根据需要实现特定的文本样式效果。 - 添加交互性:某些情况下,可能需要在文本中添加交互性,例如可点击的链接、点击事件等。
ClickableSpan是一个内置的可点击Span,可以根据自己的需求自定义更复杂的交互行为,例如自定义点击效果、处理特定的点击事件等。 - 自定义图文混排:
ImageSpan可用于在文本中插入图片,但有时可能需要更复杂的图文混排效果,例如在文本中插入带有特殊样式的图标、自定义图标与文本的对齐方式等。通过自定义Span,可以实现更灵活的图文混排效果。 - 特定的文本效果:在某些情况下,可能需要特定的文本效果,例如在文本中添加背景色、下划线、删除线、引用样式等。通过自定义
Span,可以根据具体需求实现这些特定的文本效果。 - 自定义文本布局:
LeadingMarginSpan可以用于段落缩进,但在某些情况下,可能需要更复杂的文本布局,例如为文本添加自定义的间距、设置行高等。通过自定义Span,可以实现更高级的文本布局效果。
四 小结
本文主要是介绍了一些理论知识,包括Span 的定义、分类、自定义等,因为篇幅原因,Span 的使用示例放在下一篇中。
五 资料
【1】https://developer.android.google.cn/develop/ui/views/text-and-emoji/spans
【2】https://medium.com/androiddevelopers/spantastic-text-styling-with-spans-17b0c16b4568
【3】[译] 论 Android 中 Span 的正确打开方式
欢迎扫描下方二维码或搜索微信公众号 代码说, 关注我的微信公众号查看最新文章~