194

I am creating a ClickableSpan, and it is displaying properly with the proper text underlined. However, the clicks are not registering. Do you know what I am doing wrong???

Thanks, Victor

Here is the code snippet:

view.setText("This is a test");
ClickableSpan span = new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        log("Clicked");
    }
};
view.getText().setSpan(span, 0, view.getText().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Andrei Tanana
  • 7,932
  • 1
  • 27
  • 36
Victor Grazi
  • 15,563
  • 14
  • 61
  • 94

4 Answers4

544

Have you tried setting the MovementMethod on the TextView that contains the span? You need to do that to make the clicking work...

tv.setMovementMethod(LinkMovementMethod.getInstance());
Marc Attinasi
  • 5,644
  • 1
  • 16
  • 7
  • Do not work well if `tv` is of type EditText, true you can click on the span but not edit this as normal. – FIG-GHD742 Sep 12 '12 at 23:33
  • 1
    thank a lot! It's work for me too! can you explicate me wherefore about this setting? – alfo888_ibg Dec 05 '13 at 13:50
  • 86
    OF COURSE I need to set what the documentation calls an "arrow key handler" to make a click handler work. So obvious! (╯°□°)╯︵ ┻━┻ – adamdport Mar 30 '15 at 21:39
  • It works, but I'll never really know why that is not the default behavior. – EpicPandaForce May 18 '18 at 17:58
  • 1
    And Google forgot to mention that calling setMovementMethod makes the "ellipsize" not work... So it seems the correct approach is to manually implement a TouchListener and take it from there... – slott Dec 14 '19 at 14:22
  • I'm so sorry I misclicked downvote while I want to fav this answer. While this does make clicking work, it makes the whole TextView consume the click event, not just the span. If the TextView's background needs to be clickable (such as a Recycler ViewHolder), a bigger chunk of it (size of the TextView) than expected (size of the span) will be blocked. I end up creating a separate TextView for it... – Lionet Chen Nov 09 '20 at 01:07
  • Tkanks. You made my day bro. – Man1s Nov 11 '20 at 10:01
  • Thank you so much. Worked like a charm. I spent to much time on this. You saved my time. @Man1s – Riddhi Shankar May 19 '23 at 10:27
14

Kotlin util function:

fun setClickable(textView: TextView, subString: String, handler: () -> Unit, drawUnderline: Boolean = false) {
    val text = textView.text
    val start = text.indexOf(subString)
    val end = start + subString.length

    val span = SpannableString(text)
    span.setSpan(ClickHandler(handler, drawUnderline), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

    textView.linksClickable = true
    textView.isClickable = true
    textView.movementMethod = LinkMovementMethod.getInstance()

    textView.text = span
}

class ClickHandler(
        private val handler: () -> Unit,
        private val drawUnderline: Boolean
) : ClickableSpan() {
    override fun onClick(widget: View?) {
        handler()
    }

    override fun updateDrawState(ds: TextPaint?) {
        if (drawUnderline) {
            super.updateDrawState(ds)
        } else {
            ds?.isUnderlineText = false
        }
    }
}

Usage:

Utils.setClickable(textView, subString, {handleClick()})
eleven
  • 6,779
  • 2
  • 32
  • 52
9

After some trial and error, the sequence of setting the tv.setMovementMethod(LinkMovementMethod.getInstance()); does matter.

Here's my full code

String stringTerms = getString(R.string.sign_up_terms);
Spannable spannable = new SpannableString(stringTerms);
int indexTermsStart = stringTerms.indexOf("Terms");
int indexTermsEnd = indexTermsStart + 18;
spannable.setSpan(new UnderlineSpan(), indexTermsStart, indexTermsEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(getColor(R.color.theme)), indexTermsStart, indexTermsEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Log.d(TAG, "TODO onClick.. Terms and Condition");
    }
}, indexTermsStart, indexTermsEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

int indexPolicyStart = stringTerms.indexOf("Privacy");
int indexPolicyEnd = indexPolicyStart + 14;
spannable.setSpan(new UnderlineSpan(), indexPolicyStart, indexPolicyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(getColor(R.color.theme)), indexPolicyStart, indexPolicyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Log.d(TAG, "TODO onClick.. Privacy Policy");
    }
}, indexPolicyStart, indexPolicyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

TextView textViewTerms = (TextView) findViewById(R.id.sign_up_terms_text);
textViewTerms.setText(spannable);
textViewTerms.setClickable(true);
textViewTerms.setMovementMethod(LinkMovementMethod.getInstance());
Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38
twelvester
  • 149
  • 1
  • 3
6

Direct Approach in Kotlin

  val  textHeadingSpannable = SpannableString(resources.getString(R.string.travel_agent))


           val clickSpan = object : ClickableSpan(){
               override fun onClick(widget: View) {

                // Handel your click
               }
           }
            textHeadingSpannable.setSpan(clickSpan,104,136,Spannable.SPAN_INCLUSIVE_EXCLUSIVE)

            tv_contact_us_inquire_travel_agent.movementMethod = LinkMovementMethod.getInstance()
            tv_contact_us_inquire_travel_agent.text = textHeadingSpannable
Mughil
  • 705
  • 7
  • 8