1. 简介
在安卓应用开发的过程中经常会遇到要求我们在一个文章中使用不同的style。解决这样的需求有如下三种方法。
- 使用多个TextView进行拼接
- 使用HTML
- 使用SpannableString
虽然说是有三种方法,但是都有各自的缺陷,我们应根据情况进行选择。
-
使用多个TextView进行拼接的缺点是需要比单个TextView花更多的绘制时间,容易造成UI卡顿,还有灵活性不足。因为是拼接,遇到需要自动换行等问题是比较难解决。
-
使用HTML的话,需要设计到连接点网络获取HTML或者本地读取文件,逻辑会比较复杂,且稍微耗时。而且如果需要设置点击行为,还会涉及到js注入的问题。
-
使用SpannbleString没有明显的缺点,且使用灵活。要是鸡蛋里点骨头的话就是代码不易读。因为涉及到String长度设置的问题。
回到正题,Spannable有两个实现类,SpannableString
和 SpannableStringBuilder
。关系如下。
SpannableString
和 SpannableStringBuilder
的关系和 String
和 StringBuilder
比较相似。
2. SetSpan
SpannableString
和 SpannableStringBuilder
都是通过SetSpan
方法来设置具体的style。
具体方法是 setSpan(Any what, int Start, int end, int flag)
。
这里的Any what
里传入的参数是Span类。start
和 end
传入的是要设置style的起始和终止位置,这里需要注意的是end不包括最后一位。flag
是具体的策略,下面会讲到。
2.1 Span类
Span类的作用是告诉SpannableString要设置什么样的style。Span类有很多种,如下。
Span类 | 作用 | 备注 |
---|---|---|
BackgroundColorSpan | 设置文本背景颜色 | 参数传入一个int类型的颜色 |
ForegroundColorSpan | 设置文本颜色 | 参数传入一个int类型的颜色 |
ClickableSpan | 设置点击事件 | 需要继承这个类重写onClick方法 |
StrikethroughSpan | 设置删除线效果 | |
UnderlineSpan | 设置下划线效果 | |
AbsoluteSizeSpan | 设置文字的绝对大小 | 第一个参数为字体大小,只有这一个参数时,单位为px,第二个参数dip,默认为false,设为true时,第一个参数size的单位是dp |
RelativeSizeSpan | 设置文字的相对大小 | |
StyleSpan | 设置文字粗体、斜体 | Typeface.BOLD为粗体,Typeface.ITALIC为斜体, Typeface.BOLD_ITALIC为粗斜体 |
ImageSpan | 设置图片 | 将[start,end)范围内的文字替换成参数传入的图片 |
MaskFilterSpan | 修饰效果,如模糊(BlurMaskFilter)浮雕 | |
RasterizerSpan | 光栅效果 | |
SuggestionSpan | 相当于占位符 | |
DynamicDrawableSpan | 设置图片,基于文本基线或底部对齐 | |
ScaleXSpan | 基于x轴缩放 | |
SubscriptSpan | 下标(数学公式会用到) | |
SuperscriptSpan | 上标(数学公式会用到) | |
TextAppearanceSpan | 文本外貌(包括字体、大小、样式和颜色) | |
TypefaceSpan | 文本字体 | |
URLSpan | 文本超链接 |
2.2 Flag
Flag有如下四种情况。
Flag | 作用 |
---|---|
SPAN_EXCLUSIVE_EXCLUSIVE | 在文本前面或后面插入新的文本时,都不会应用该样式 |
SPAN_EXCLUSIVE_INCLUSIVE | 在文本前插入新的文本不会应用该样式,而在文本后插入新文本会应用该样式 |
SPAN_INCLUSIVE_EXCLUSIVE | 在文本前插入新的文本会应用该样式,而在文本后插入新文本不会应用该样式 |
SPAN_INCLUSIVE_INCLUSIVE | 在文本前面或后面插入新的文本时,都会应用该样式 |
3. SpannableString
我们用BackgroundColorSpan和UnderlineSpan作为例子,具体代码实现如下
// 实例化SpannableString, 同时把需要调整sytl的string传入
val baSpannable = SpannableString("Background and Underline")
baSpannable.setSpan(
BackgroundColorSpan(Color.YELLOW),
0,
11,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
baSpannable.setSpan(
UnderlineSpan(),
16,
baSpannable.toString().length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
// 把SpannableString直接传入给TextView的text
// 千万不要把SpannableString转化成string再传入,style会全部失效
backgroundAndUnderline.text = baSpannable
4. SpannableStringBuilder
SpannableStringBuilder
的作用和 StringBuilder
很类似。把string传入builder中,然后进行一系列操作。
具体代码如下。
val spannableStringBuilder = SpannableStringBuilder().also {
// 把string传入到SpannableStringBuilder中
it.append("hello world! click here!")
// 直接设置style
it.setSpan(
// 设置style的Span, 第一个参数是context, 第二个是style的int值
TextAppearanceSpan(this, R.style.style),
0,
12,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
it.setSpan(
// 设置可点击的Span,下面有讲到
clickableSpannable,
13,
it.toString().length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
it.setSpan(
TextAppearanceSpan(this, R.style.clickStyle),
13,
it.toString().length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
textView.text = spannableStringBuilder
// 注意: 必须要设置下面的代码, 否则没有点击效果
textView.movementMethod = LinkMovementMethod.getInstance()
5. ClickableSpan
上面代码中出现的 ClickableSpan
是可点击的Span。即点击可实现相应的点击事件。具体实现代码如下。
val clickableSpannable = object : ClickableSpan() {
// 重写点击事件
override fun onClick(widget: View) {
Toast.makeText(this@MainActivity, "hello", Toast.LENGTH_LONG).show()
}
// 重写具体的样式, 这里只能直线颜色,下划线等等。
override fun updateDrawState(ds: TextPaint) {
// link的颜色
ds.linkColor = Color.BLUE
// 下划线的宽度
ds.underlineThickness = 0.5F
// 下划线有效
ds.isUnderlineText = true
}
}
最后把 SpannableString
赋值给 TextView
的text以后需要调用下面的代码才能让点击事件有效。这个代码的具体实现原理其实就是 TouchEvent
的传递。还有需要注意的是设置 ClickableSpan
,可能会与 TextView
本身的onClick事件冲突。
// 注意: 必须要设置下面的代码, 否则没有点击效果
textView.movementMethod = LinkMovementMethod.getInstance()
github: github.com/HyejeanMOON…