如何在公共类复用ClickableSpan的updateDrawState()实现TextView自定义点击
两种优雅的实现方案,搞定ClickableSpan通用逻辑复用
这个需求在Android开发里挺常见的,既要复用通用的updateDrawState()逻辑,又要保留不同TextView的点击差异化,我给你分享两种实用的方案:
方案一:抽象基类封装通用逻辑
先创建一个抽象的基类Span,把所有TextView都一致的updateDrawState()逻辑写在这里,然后把onClick()设为抽象方法,让子类去实现各自的点击逻辑:
基类代码(Kotlin示例,Java写法类似)
abstract class BaseClickableSpan : ClickableSpan() { override fun updateDrawState(ds: TextPaint) { // 这里写你的通用样式逻辑,比如去掉下划线、统一文字颜色 ds.isUnderlineText = false ds.color = Color.parseColor("#1677FF") // 其他通用设置,比如文字大小、加粗等 } // 抽象方法,交给子类实现差异化点击逻辑 abstract override fun onClick(widget: View) }
子类实现各自的点击逻辑
每个TextView对应的Span只需要继承基类,实现onClick()即可:
// 对应第一个TextView的Span class FirstCustomSpan : BaseClickableSpan() { override fun onClick(widget: View) { // 这里写第一个TextView的专属点击逻辑,比如弹Toast Toast.makeText(widget.context, "第一个TextView被点击啦", Toast.LENGTH_SHORT).show() } } // 对应第二个TextView的Span class SecondCustomSpan : BaseClickableSpan() { override fun onClick(widget: View) { // 第二个TextView的差异化逻辑,比如跳转页面 val intent = Intent(widget.context, DetailActivity::class.java) widget.context.startActivity(intent) } }
给TextView设置Span
// 第一个TextView的设置 val firstSpannable = SpannableString("点击这里查看提示") firstSpannable.setSpan( FirstCustomSpan(), 3, 7, // 要设置点击的文字区间 Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) firstTextView.text = firstSpannable firstTextView.movementMethod = LinkMovementMethod.getInstance() // 第二个TextView的设置同理 val secondSpannable = SpannableString("点击跳转详情") secondSpannable.setSpan( SecondCustomSpan(), 2, 4, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) secondTextView.text = secondSpannable secondTextView.movementMethod = LinkMovementMethod.getInstance()
方案二:带回调的通用Span(无需创建多个子类)
如果不想为每个TextView都创建一个子类,可以用回调接口的方式,把点击逻辑从外部传入,这样一个Span类就能搞定所有场景:
通用Span类代码
class CallbackClickableSpan( // 传入点击回调,直接用lambda更简洁 private val onSpanClick: (View) -> Unit ) : ClickableSpan() { override fun updateDrawState(ds: TextPaint) { // 同样的通用样式逻辑 ds.isUnderlineText = false ds.color = Color.parseColor("#1677FF") } override fun onClick(widget: View) { // 调用外部传入的回调逻辑 onSpanClick.invoke(widget) } }
给TextView设置Span(直接传入点击逻辑)
// 第一个TextView的设置 val firstText = SpannableString("点击触发提示") firstText.setSpan( CallbackClickableSpan { view -> Toast.makeText(view.context, "第一个TextView点击事件", Toast.LENGTH_SHORT).show() }, 3, 7, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) firstTextView.text = firstText firstTextView.movementMethod = LinkMovementMethod.getInstance() // 第二个TextView的设置,仅需更换回调逻辑 val secondText = SpannableString("点击跳转页面") secondText.setSpan( CallbackClickableSpan { view -> view.context.startActivity(Intent(view.context, TargetActivity::class.java)) }, 2, 4, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) secondTextView.text = secondText secondTextView.movementMethod = LinkMovementMethod.getInstance()
方案选择建议
- 如果你的点击逻辑比较复杂,且需要在多个地方复用,优先选方案一,代码结构更清晰;
- 如果点击逻辑比较简单,或者每个TextView的逻辑都是一次性的,方案二更灵活,不用创建过多子类。
内容的提问来源于stack exchange,提问作者Masa
相关产品推荐
相关产品推荐

