如果你昨天的成绩了不起,说明你今天做得还不够好。
至这一篇文章,我个人搭建的博客成功运营两年了,每两周更新一篇想写的冰山一角。两年,不长,好多都物是人非,莫忘初衷,就继续下去吧,Move on。好,开始正题。
Android 中,显示文本都是基于 TextView 控件,往往样式没什么额外惊喜。若我们想自定义个性的颜色、样式,甚至带超链接等,就不得不请出将要谈到的 SpannableString。
简要介绍
SpannableString 是 Android 内置的专门处理富文本的类,先简单看看其常用的方法:
类型 | 方法 | 描述 |
---|---|---|
final char | charAt(int i) | 和 String 一样,可以返回指定位置的字符值 |
boolean | equals(Object o) | 和 String 一样,指示是否有别的对象和当前对象相等 |
final void | getChars (int start, int end, char[] dest, int off) | 和 String 一样,从字符序列中取出 start ~ end - 1 的字符,在偏移为 off 处,复制到 dest 数组中 |
int | getSpanEnd(Object what) | 返回指定标记对象依附文本范围的末端,若未依附则返回 -1 |
int | getSpanFlags(Object what) | 当 setSpan(Object, int, int, int) 用来依附指定的标记对象时,返回指定的 flags,若未依附则返回 0 |
int | getSpanStart(Object what) | 返回指定标记对象依附文本范围的开端,若未依附则返回 -1 |
getSpans(int queryStart, int queryEnd, Class |
返回依附指定序列块标记对象的数组,其类型是指定的类型,或者是子类 | |
int | hashCode() | 返回对象的哈希值 |
final int | length() | 返回该字符序列的长度 |
接下面,
类型 | 方法 | 描述 |
---|---|---|
int | nextSpanTransition(int start, int limit, Class kind) | 在标记类对象类型开始或结束的地方,返回比 start 大的第一个偏移,或者是 limit,条件是没有 starts 或 ends 比 start 大,且比 limit 小 |
void | removeSpan(Object what) | 移除 Span |
void | setSpan(Object what, int start, int end, int flags) | 设置 Span |
final CharSequence | subSequence(int start, int end) | 返回当前序列截取后的子序列 |
final String | toString() | 返回当前对象转化成的 String |
static SpannableString | valueOf(CharSequence source) | 生成 SpannableString 类型的值 |
以上总结来自官网 SpannableString,若实现可拼接,见 SpannableStringBuilder。
Demo 用法
Demo 地址:SpannableStringDemo。
显示如下:
点击“现在我仍然……都要努力”,响应点击事件;点击下方的“电话……进入地图”,分别跳转至相应的 URL 地址页面。
代码如下,先看普通文本部分:
1 | private SpannableString getNormalString() { |
再看超链接部分:
1 | private SpannableString getLinkString() { |
深入解读
通过以上简单的使用,我们可以实现常规文本的定制化,进一步,来看看 Android 内部 TextView 富文本的实现。UML 图如下:
一目了然,先看 Demo 中用的最多的 setSpan() 方法,其走入的源码如下:
1 | private void setSpan(Object what, int start, int end, int flags |
该方法内部主要改变了 mSpans,mSpanData 和 mSpanCount。其中,mSpanData 表示样式的首尾索引和 flags,mSpans 表示对应的样式。从源码中我们可以看到,mSpanData 将 start、end 和 flags 打包存在一起,根据对应的偏移地址取值。然而,SpannableStringBuilder 直接将四个相应的变量存在四个数组里。进一步,来看看富文本的绘制,走到 TextView 的 onDraw() 里:
1 | protected void onDraw(Canvas canvas) { |
TextView 的绘制细节交给 mLayout,其由 makeSingleLayout() 赋值:
1 | protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, |
接着,走到 layout.draw() 的 draw() 方法里,在指定的 Canvas 上绘制布局,其背景和文本之间有着高亮的路径:
1 | public void draw(Canvas canvas, Path highlight, Paint highlightPaint, |
再走到绘制文本的方法 drawText() 里,Layout 计算好每一行的段落格式,如前面空出多少、居中还是靠右等。
1 | public void drawText(Canvas canvas, int firstLine, int lastLine) { |
再走到 TextLine 中,其负责文本绘制的文字显示样式,重点看 handleText() 方法:
1 | private float handleText(TextPaint wp, int start, int end, |
其在 handleRun() 里被执行:
1 | private float handleRun(int start, int measureLimit, |
大体流程如此。至此,浅谈 Android 中的富文本之强大的 SpannableString 到此完毕,更多细节还需要在使用中体会。
本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。
1 | Email: [email protected] / WeChat: Wolverine623 |
您也可以关注我个人的微信公众号 :码农六哥,第一时间获得博客的更新通知,或后台留言与我交流。
参考文献
1.https://developer.android.com/reference/android/text/SpannableString
2.https://blog.csdn.net/lukejunandroid/article/details/25892737