首先Span是什么
Span是功能强大的标记对象,可用于在字符或段落级别的文本设置样式。我们可以将该标记对象添加到文本上,从而可以改变文本的颜色,使文本可点击,缩放文本大小等等。
创建Span
创建一个Span,可以使用以下三个类
SpannedString 使用该类创建的Span对象不可以改变文本,不可以改变标记。很少使用到。
SpannableString 使用该类创建的Span对象不可改变文本,可以改变标记,一般在不修改文本内容,只修改文本标记的情况下(比如修改文本的颜色等)应该使用该类。
SpannableStringBuilder 使用该类创建的Span对象可以改变文本,可以改变标记,一般在要修改文本内容时使用,如果需要将大量的Span附加到文本上,那不管是否修改文本,都应该使用该类。
应用Span
如果需要应用Span,需要对Spannable对象调用setSpan(Object _what_, int _start_, int _end_, int _flags_)。参数what代表要应用于文本的Span,start和end表示要应用该Span的文本位置(前包括后不包括,要对整个文本应用Span,start为0,end为文本长度)
在应用Span后,如果在Span边界内插入文本,则Span会自动扩展已插入的文本,如果在Span的边界上(即在start或end索引处)插入文本时。flags参数表示是否应该将Span扩展到插入的文本上。
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 标志表示会排除插入的文本
Spannable.SPAN_EXCLUSIVE_INCLUSIVE 标志表示会包含插入的文本
常用的Span
ForegroundColorSpan 可用于改变文本颜色
val spannable = SpannableString("只要学不死,就往死里学!")
spannable.setSpan(
ForegroundColorSpan(Color.RED),
0,
6,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)
textView.text = spannable
BackgroundColorSpan 可用于改变文本的背景颜色
val spannable = SpannableString("只要学不死,就往死里学!")
spannable.setSpan(
BackgroundColorSpan(Color.RED),
0,
6,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)
textView.text = spannable
UnderlineSpan 可用于给文本添加下划线
val spannable = SpannableString("只要学不死,就往死里学!")
spannable.setSpan(
UnderlineSpan(),
0,
6,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)
textView.text = spannable
ClickableSpan 可用于给文本添加点击事件
private val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
Toast.makeText(this@MainActivity, "clickableSpan", Toast.LENGTH_SHORT).show()
}
override fun updateDrawState(ds: TextPaint) {
//clickableSpan默认的文本有字体颜色和下划线
//可重写此方法改变默认行为
ds.isUnderlineText = false
}
}
val spannable = SpannableString("只要学不死,就往死里学!")
spannable.setSpan(
clickableSpan,
0,
6,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)
textView.text = spannable
//默认的clickableSpan点击后会高亮,设置textView的highlightColor属性来消除高亮
textView.highlightColor = ContextCompat.getColor(this, android.R.color.transparent)
//设置movementMethod后才有真正的有点击事件
textView.movementMethod = LinkMovementMethod.getInstance()
在使用ClickableSpan时,可能会遇到几个问题.。
一.如果在使用ClickableSpan的同时为TextView添加上点击事件,在点击Span的区域时,会同时触发两个回调事件。这是因为TextView的onTouchEvent()会首先调用super.onTouchEvent()。然后才会调用我们的mMovement.onTouchEvent()。由于View的onClick事件会被post到主线程调用。有一定的延迟。所以Span的点击事件会优先于View的onClick事件。我们可以设置一个标志位。来过滤掉View的onClick事件
//设置标志位来过滤事件
var isClickSpan = false
textView = findViewById(R.id.textView)
textView.setOnClickListener {
//如果span的点击事件没触发
if(!isClickSpan) {
Log.d("TAG", "textView")
}
//重置标志位
isClickSpan = false
}
private val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
//更改标志位
isClickSpan = true
Log.d("TAG", "clickableSpan")
}
}
val spannable = SpannableString("只要学不死,就往死里学!")
spannable.setSpan(
clickableSpan,
0,
6,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)
textView.text = spannable
textView.movementMethod = LinkMovementMethod.getInstance()
二.如果为一个TextView添加上ClickableSpan,则不管点击的是不是Span区域。TextView都会默认消耗掉此次事件。这样会导致该TextView所在的ViewGroup接收不到事件回调。因为默认事件都被TextView消耗掉了。我们可以重写LinkMovementMethod的onTouchEvent()
class CustomLinkMovementMothod : LinkMovementMethod() {
companion object {
private var instance: CustomLinkMovementMothod? = null
get() {
if (field == null) {
field = CustomLinkMovementMothod()
}
return field
}
@JvmStatic
fun get() : CustomLinkMovementMothod {
return instance!!
}
}
override fun onTouchEvent(widget: TextView?, buffer: Spannable?, event: MotionEvent?): Boolean {
val b = super.onTouchEvent(widget, buffer, event)
if (!b && event?.action == MotionEvent.ACTION_UP) {
val viewParent = widget?.parent
if (viewParent is ViewGroup) {
//显示的调用performClick
//来触发onClick的回调
viewParent.performClick()
}
}
return b
}
}
重复利用Span
当我们使用setText()重载时,TextView会创建Spannable的副本作为SpannedString,并将其作为CharSequence保存在内存中。这意味着文本和Span不可变。而当我们需要更新文本或Span时,会需要创建一个新的Spannable对象并再次调用setText()。
如果表示我们创建的Span可变。可调用setText(CharSequence text, TextView.BufferType type)
textView.setText(spannable, TextView.BufferType.SPANNABLE)
val spannableText = textView.text as Spannable
spannableText.setSpan(BackgroundColorSpan(Color.RED),
0,
6,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)
现在,当附加,分离,或重新定位Span时,TextView会自动更新来反映对文本的更改。