之前在『Android 改变 TextView 内局部样式』中介绍过利用 HTML 标签来设置样式,使用时你可能会遇到超链接跳转的需求,很容易你就能想到给 HTML 字符串添加 <a> 标签:

class MainActivity : AppCompatActivity() {
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        val content = Html.fromHtml(
            "这是一个超链接:<a href='https://google.com'>Google</a>",
            Html.FROM_HTML_MODE_LEGACY
        )
        binding.textView.text = content
    }
}

TextView 确实出现了超链接的显示效果,但是却不能够响应点击。

TextView 超链接效果

我们还需要对其做进一步处理。这里同样用到了『Android 改变 TextView 内局部样式』中介绍的另一种方式,也就是 SpannableString,代码如下:

class MainActivity : AppCompatActivity() {
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        val content = Html.fromHtml(
            "这是一个超链接:<a href='https://google.com'>Google</a>",
            Html.FROM_HTML_MODE_LEGACY
        )
        binding.textView.text = content
        handleHtmlClick(binding.textView)
    }
    
    private fun handleHtmlClick(textView: TextView) {
        textView.movementMethod = LinkMovementMethod.getInstance()
        val text = textView.text
        if (text is Spannable) {
            val end = text.length
            val spans = text.getSpans(0, end, URLSpan::class.java)
            val sb = SpannableStringBuilder(text)
            sb.clearSpans()
            for (span in spans) {
                sb.setSpan(
                    URLSpan(span.url),
                    text.getSpanStart(span),
                    text.getSpanEnd(span),
                    Spanned.SPAN_EXCLUSIVE_INCLUSIVE
                )
            }
            textView.text = sb
        }
    }
}

这里通过 Spanned.getSpans() 方法提取到可点击的 URLSpan,并重新构造 URLSpan 再添加到 SpannableString 中即可。URLSpanClickableSpan 的实现,当点击其中设置了 Span 的文本时,URLSpan 将尝试通过启动带有 Intent.ACTION_VIEWActivity 来打开 URL。

上面代码的效果就是跳转到浏览器打开超链接。

超链接跳转

如果希望接管点击后的逻辑,我们可以自行实现 ClickableSpan

class MainActivity : AppCompatActivity() {
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        val content = Html.fromHtml(
            "这是一个超链接:<a href='https://google.com'>Google</a>",
            Html.FROM_HTML_MODE_LEGACY
        )
        binding.textView.text = content
        handleHtmlClick(binding.textView)
    }
    
    private fun handleHtmlClick(textView: TextView) {
        textView.movementMethod = LinkMovementMethod.getInstance()
        val text = textView.text
        if (text is Spannable) {
            val end = text.length
            val spans = text.getSpans(0, end, URLSpan::class.java)
            val sb = SpannableStringBuilder(text)
            sb.clearSpans()
            for (span in spans) {
                sb.setSpan(
                    HypertextSpan(this, span.url),
                    text.getSpanStart(span),
                    text.getSpanEnd(span),
                    Spanned.SPAN_EXCLUSIVE_INCLUSIVE
                )
            }
            textView.text = sb
        }
    }
}

class HypertextSpan(private val context: Context, private val url: String) : ClickableSpan() {
    override fun onClick(widget: View) {
        // 处理点击逻辑
    }
}

我们构造一个 HypertextSpan 来接管点击逻辑,只需将上一步的 URLSpan 替换即可,构造方法中传入 Context 和 URL 可以方便我们处理判断及跳转。

比如在应用内打开网页:

应用内接管超链接处理