handle textview link click in my android app

Viewed 111390

I'm currently rendering HTML input in a TextView like so:

tv.setText(Html.fromHtml("<a href='test'>test</a>"));

The HTML being displayed is provided to me via an external resource, so I cannot change things around as I will, but I can, of course, do some regex tampering with the HTML, to change the href value, say, to something else.

What I want is to be able to handle a link click directly from within the app, rather than having the link open a browser window. Is this achievable at all? I'm guessing it would be possible to set the protocol of the href-value to something like "myApp://", and then register something that would let my app handle that protocol. If this is indeed the best way, I'd like to know how that is done, but I'm hoping there's an easier way to just say, "when a link is clicked in this textview, I want to raise an event that receives the href value of the link as an input parameter"

13 Answers

Kotlin version to @user5699130's answer:

Layout

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:autoLink="all"/>

InterceptedLinkMovementMethod

import android.text.Spannable
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.GestureDetector
import android.view.MotionEvent
import android.widget.TextView

/**
 * Usage:
 * fooTextView.movementMethod = InterceptedLinkMovementMethod(this)
 * Where 'this' implements [TextViewLinkClickListener]
 */
class InterceptedLinkMovementMethod(
    private val listener: TextViewLinkClickListener,
) : LinkMovementMethod() {

    private lateinit var textView: TextView
    private lateinit var spannable: Spannable
    private val gestureDetector: GestureDetector by lazy {
        GestureDetector(textView.context, SimpleTapListener())
    }

    override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
        textView = widget
        spannable = buffer
        gestureDetector.onTouchEvent(event)
        return false
    }

    inner class SimpleTapListener : GestureDetector.SimpleOnGestureListener() {

        override fun onDown(event: MotionEvent): Boolean = true

        override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
            val linkText = getLinkText(textView, spannable, event)
            val linkType = LinkTypes.getLinkTypeFromText(linkText)
            if (linkType != LinkTypes.NONE) {
                listener.onLinkClicked(linkText, linkType)
            }
            return false
        }

        override fun onLongPress(e: MotionEvent) {
            val linkText = getLinkText(textView, spannable, e)
            val linkType = LinkTypes.getLinkTypeFromText(linkText)
            if (linkType != LinkTypes.NONE) {
                listener.onLinkLongClicked(linkText, linkType)
            }
        }

        private fun getLinkText(widget: TextView, buffer: Spannable, event: MotionEvent): String {
            var x = event.x.toInt()
            var y = event.y.toInt()
            x -= widget.totalPaddingLeft
            y -= widget.totalPaddingTop
            x += widget.scrollX
            y += widget.scrollY
            val layout = widget.layout
            val line = layout.getLineForVertical(y)
            val off = layout.getOffsetForHorizontal(line, x.toFloat())
            val link = buffer.getSpans(off, off, ClickableSpan::class.java)
            if (link.isEmpty()) return ""
            return buffer.subSequence(buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]))
                .toString()
        }
    }
}

LinkTypes

import android.util.Patterns

enum class LinkTypes {
    PHONE,
    WEB_URL,
    EMAIL_ADDRESS,
    NONE;

    companion object {
        fun getLinkTypeFromText(text: String): LinkTypes =
            when {
                Patterns.PHONE.matcher(text).matches() -> PHONE
                Patterns.WEB_URL.matcher(text).matches() -> WEB_URL
                Patterns.EMAIL_ADDRESS.matcher(text).matches() -> EMAIL_ADDRESS
                else -> NONE
            }
    }
}

TextViewLinkClickListener

interface TextViewLinkClickListener {
    fun onLinkClicked(linkText: String, linkTypes: LinkTypes)

    fun onLinkLongClicked(linkText: String, linkTypes: LinkTypes)
}
Related