Android TextView with Clickable Links: how to capture clicks?

111,752

Solution 1

Based upon another answer, here's a function setTextViewHTML() which parses the links out of a HTML string and makes them clickable, and then lets you respond to the URL.

protected void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span)
{
    int start = strBuilder.getSpanStart(span);
    int end = strBuilder.getSpanEnd(span);
    int flags = strBuilder.getSpanFlags(span);
    ClickableSpan clickable = new ClickableSpan() {
        public void onClick(View view) {
            // Do something with span.getURL() to handle the link click...
        }
    };
    strBuilder.setSpan(clickable, start, end, flags);
    strBuilder.removeSpan(span);
}

protected void setTextViewHTML(TextView text, String html)
{
    CharSequence sequence = Html.fromHtml(html);
    SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
    URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);   
    for(URLSpan span : urls) {
        makeLinkClickable(strBuilder, span);
    }
    text.setText(strBuilder);
    text.setMovementMethod(LinkMovementMethod.getInstance());       
}

Solution 2

I made an easy extension function in Kotlin to catch url link clicks in a TextView by applying a new callback to URLSpan elements.

strings.xml (example link in text)

<string name="link_string">this is my link: <a href="https://www.google.com/">CLICK</a></string>

Make sure your spanned text is set to the TextView before you call "handleUrlClicks"

textView.text = getString(R.string.link_string)

This is the extension function:

/**
 * Searches for all URLSpans in current text replaces them with our own ClickableSpans
 * forwards clicks to provided function.
 */
fun TextView.handleUrlClicks(onClicked: ((String) -> Unit)? = null) {
    //create span builder and replaces current text with it
    text = SpannableStringBuilder.valueOf(text).apply {
        //search for all URL spans and replace all spans with our own clickable spans
        getSpans(0, length, URLSpan::class.java).forEach {
            //add new clickable span at the same position
            setSpan(
                object : ClickableSpan() {
                    override fun onClick(widget: View) {
                        onClicked?.invoke(it.url)
                    }
                },
                getSpanStart(it),
                getSpanEnd(it),
                Spanned.SPAN_INCLUSIVE_EXCLUSIVE
            )
            //remove old URLSpan
            removeSpan(it)
        }
    }
    //make sure movement method is set
    movementMethod = LinkMovementMethod.getInstance()
}

This is how I call it:

textView.handleUrlClicks { url ->
    Timber.d("click on found span: $url")
}

Solution 3

You've done as follows:

text_view.setMovementMethod(LinkMovementMethod.getInstance());
text_view.setText( Html.fromHtml( str_links ) );

have you tried in reverse order as shown below?

text_view.setText( Html.fromHtml( str_links ) );
text_view.setMovementMethod(LinkMovementMethod.getInstance());

and without:

text_view.setLinksClickable(true);

Solution 4

This can be simply solved by using Spannable String.What you really want to do (Business Requirement) is little bit unclear to me so following code will not give exact answer to your situation but i am petty sure that it will give you some idea and you will be able to solve your problem based on the following code.

As you do, i'm also getting some data via HTTP response and i have added some additional underlined text in my case "more" and this underlined text will open the web browser on click event.Hope this will help you.

TextView decription = (TextView)convertView.findViewById(R.id.library_rss_expan_chaild_des_textView);
String dec=d.get_description()+"<a href='"+d.get_link()+"'><u>more</u></a>";
CharSequence sequence = Html.fromHtml(dec);
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
UnderlineSpan[] underlines = strBuilder.getSpans(0, 10, UnderlineSpan.class);   
for(UnderlineSpan span : underlines) {
    int start = strBuilder.getSpanStart(span);
    int end = strBuilder.getSpanEnd(span);
    int flags = strBuilder.getSpanFlags(span);
    ClickableSpan myActivityLauncher = new ClickableSpan() {
        public void onClick(View view) {
            Log.e(TAG, "on click");
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(d.get_link()));
            mContext.startActivity(intent);         
        }
    };
    strBuilder.setSpan(myActivityLauncher, start, end, flags);
}
decription.setText(strBuilder);
decription.setLinksClickable(true);
decription.setMovementMethod(LinkMovementMethod.getInstance());

Solution 5

I've had the same problem but a lot of text mixed with few links and emails. I think using 'autoLink' is a easier and cleaner way to do it:

  text_view.setText( Html.fromHtml( str_links ) );
  text_view.setLinksClickable(true);
  text_view.setAutoLinkMask(Linkify.ALL); //to open links

You can set Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS if there's only one of them you want to use or set from the XML layout

  android:linksClickable="true"
  android:autoLink="web|email"

The available options are: none, web, email, phone, map, all

Share:
111,752
Zane Claes
Author by

Zane Claes

I code IOT, CNC, and more.

Updated on July 08, 2022

Comments

  • Zane Claes
    Zane Claes almost 2 years

    I have a TextView which is rendering basic HTML, containing 2+ links. I need to capture clicks on the links and open the links -- in my own internal WebView (not in the default browser.)

    The most common method to handle link rendering seems to be like this:

    String str_links = "<a href='http://google.com'>Google</a><br /><a href='http://facebook.com'>Facebook</a>";
    text_view.setLinksClickable(true);
    text_view.setMovementMethod(LinkMovementMethod.getInstance());
    text_view.setText( Html.fromHtml( str_links ) );
    

    However, this causes the links to open in the default internal web browser (showing the "Complete Action Using..." dialog).

    I tried implementing a onClickListener, which properly gets triggered when the link is clicked, but I don't know how to determine WHICH link was clicked...

    text_view.setOnClickListener(new OnClickListener(){
    
        public void onClick(View v) {
            // what now...?
        }
    
    });
    

    Alternatively, I tried creating a custom LinkMovementMethod class and implementing onTouchEvent...

    public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) {
        String url = text.toString();
        // this doesn't work because the text is not necessarily a URL, or even a single link... 
        // eg, I don't know how to extract the clicked link from the greater paragraph of text
        return false;
    }
    

    Ideas?


    Example solution

    I came up with a solution which parses the links out of a HTML string and makes them clickable, and then lets you respond to the URL.

    • Renjith
      Renjith over 11 years
      Why dont you use Spannable String.??
    • Zane Claes
      Zane Claes over 11 years
      In reality, the HTML is provided by a remote server, not generated by my application.
    • Jonik
      Jonik over 10 years
      Your example solution is very helpful; using that approach I capture clicks nicely and can launch another Activity, with parameters, depending on which link was clicked. (Key point to understand was "Do something with span.getURL()".) You could even post it as an answer, as it's better than currently accepted answer!
  • Zane Claes
    Zane Claes over 11 years
    Great! I've modified this for my case. I'll edit my post to include the code.
  • android developer
    android developer over 11 years
    is there a similar solution that can be used inside xml ?
  • Jonik
    Jonik over 10 years
    I got it working with OP's modified version (in the question), not with this. (With this version, the clicks went straight to "complete action using" dialog.)
  • Jonik
    Jonik over 10 years
    Worked great. With this approach (unlike the other answers), I managed to 1) capture clicks and 2) launch another Activity, with parameters, depending on which link was clicked.
  • Ray
    Ray over 10 years
    I used this logic, however had to replace the UnderlineSpan with URLSpan.Also needed to remove the old spans from the SpannableStringBuilder.
  • maverickosama92
    maverickosama92 almost 10 years
    perfect... saved my day
  • voghDev
    voghDev almost 10 years
    Wonderful, but if you apply it to a ListView (i mean, to each element's inner TextView), makes the list unclickable, though links are still clickable
  • Salman Khan
    Salman Khan over 9 years
    Whats is variable 'd' here ?
  • Sufian
    Sufian over 9 years
    @voghDev this happens with ListViews when a View's focusable is set true. This usually happens with Buttons/ImageButtons. Try calling setFocusable(false) on your TextView.
  • Rajath
    Rajath almost 9 years
    Make sure to use text.setMovementMethod(LinkMovementMethod.getInstance()); if not using URLSpan
  • donmezburak
    donmezburak almost 9 years
    it removes but does not add new spans :/
  • lightsaber
    lightsaber over 8 years
    It works but without any signal that the user clicked the link, I need a cue/animation/highlight when the link is clicked... what should I do?
  • ademar111190
    ademar111190 over 8 years
    @StarWars you can use (StateList)[developer.android.com/intl/pt-br/guide/topics/re‌​sources/… in pure android, but with HTML I don't know.
  • PH88
    PH88 over 8 years
    I've wrap these into a simpler API in github.com/bluecabin/Textoo for sharing.
  • Geeky Singh
    Geeky Singh over 8 years
    Didn't work for me when using it in AlertDialog with custom view.
  • Nick
    Nick about 8 years
    I had to add setMovementMethod(LinkMovementMethod.getInstance()); in the makeLinkClickable method for this to work.
  • X09
    X09 about 8 years
    Please, check your code again, from line text_view.setMovementMethod(new TextViewClickMovement(this, context)); ; Android Studio is complaining that context could not be resolved.
  • Admin
    Admin about 8 years
    If you copy source code from bitbucket, you should change place of context and listener like this text_view.setMovementMethod(new TextViewClickMovement( context. this));
  • X09
    X09 about 8 years
    That'll be parsing two context for the parameters. Didn't work Sir. Though the accepted answer is working for me now
  • tir38
    tir38 over 7 years
    Because this method replaces URLSpans with ClickableSpans, you won't be able to use Espresso to click on items (i.e. ViewActions.openLinkWithText).
  • Pär Nils Amsen
    Pär Nils Amsen over 7 years
    Finally something that works, tried a lot of solutions for this.
  • Vulovic Vukasin
    Vulovic Vukasin over 7 years
    Thank you for your answer sir, best one out there for this kind, thank you!
  • DmitryKanunnikoff
    DmitryKanunnikoff about 7 years
    Thank you! Works.
  • jL4
    jL4 about 6 years
    Did not work until I replaced SpannableStringBuilder strBuilder = new... with Spannable strBuilder = (Spannable) sequence;
  • Manmohan Soni
    Manmohan Soni almost 6 years
    Hi is there any way to intercept the intent fired at the time of click of a link
  • Valentin
    Valentin over 5 years
    You saved my day. Thank you :)
  • Jordi
    Jordi over 5 years
    It's been 6 years from this answer.. Of course it may have changed in latest Android version ^^ It doesn't mean it doesn't worked back then ^^
  • SKG
    SKG almost 5 years
    Awesome sauce! Thank you!
  • Aleksandr Urzhumtcev
    Aleksandr Urzhumtcev almost 5 years
    Be aware, when you use ClickableSpan instead of URLSpan, you loose logic which opens the link in browser. Use URLSpan or implement this logic by yourself.
  • Sumit Kumar
    Sumit Kumar over 4 years
    I spent 2 hour on this and finally two functions solve my problem. Thanks a lot and also learn from this code.
  • tm1701
    tm1701 over 4 years
    Absolutely SUPERB!
  • Neela
    Neela about 4 years
    It works for me with the following lines, val strBuilder = SpannableStringBuilder(htmlString) sunset_message.text = strBuilder sunset_message.linksClickable = true sunset_message.movementMethod = LinkMovementMethod.getInstance()
  • Milind Mevada
    Milind Mevada about 4 years
    Is it possible to override a onClick event using this?
  • Valentin Yuryev
    Valentin Yuryev almost 4 years
    Awesome!_______
  • Damanpreet Singh
    Damanpreet Singh about 3 years
    Nice extension but setting text to the text view directly by getting string is a bit misleading.. You need to set it using textView.text = HtmlCompat.fromHtml(htmlText, HtmlCompat.FROM_HTML_MODE_COMPACT)
  • Niroshan
    Niroshan almost 3 years
    this working only for hyperlinks. If text contain email, phone number then not clickable.
  • artenson.art98
    artenson.art98 over 2 years
    If you want to change the highlight color of the link, set a color using android:textColorHighlight="yourColor" on your TextView
  • Anh Duy
    Anh Duy about 2 years
    This code works fine even when setting android:textIsSelectable="true" in xml. Thank you very much!
  • Kannan_SJD
    Kannan_SJD about 2 years
    Give this man an award!