154

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.

Community
  • 1
  • 1
Zane Claes
  • 14,732
  • 15
  • 74
  • 131
  • 1
    Why dont you use Spannable String.?? – Renjith Sep 14 '12 at 04:50
  • 1
    In reality, the HTML is provided by a remote server, not generated by my application. – Zane Claes Sep 14 '12 at 05:00
  • 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! – Jonik Nov 14 '13 at 15:53

14 Answers14

270

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());       
}
Community
  • 1
  • 1
Zane Claes
  • 14,732
  • 15
  • 74
  • 131
  • 10
    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. – Jonik Nov 16 '13 at 21:55
  • 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 – voghDev May 27 '14 at 15:45
  • @voghDev this happens with `ListView`s when a `View`'s `focusable` is set true. This usually happens with `Button`s/`ImageButton`s. Try calling `setFocusable(false)` on your `TextView`. – Sufian Sep 11 '14 at 10:59
  • Make sure to use `text.setMovementMethod(LinkMovementMethod.getInstance());` if not using URLSpan – Rajath Jun 29 '15 at 01:53
  • it removes but does not add new spans :/ – donmezburak Jul 08 '15 at 10:38
  • I've wrap these into a simpler API in https://github.com/bluecabin/Textoo for sharing. – PH88 Jan 14 '16 at 07:08
  • Didn't work for me when using it in AlertDialog with custom view. – Geeky Singh Feb 04 '16 at 07:17
  • I had to add `setMovementMethod(LinkMovementMethod.getInstance());` in the makeLinkClickable method for this to work. – Nick Apr 25 '16 at 20:09
  • Because this method replaces `URLSpan`s with `ClickableSpan`s, you won't be able to use Espresso to click on items (i.e. `ViewActions.openLinkWithText`). – tir38 Jan 02 '17 at 20:40
  • 1
    Finally something that works, tried a lot of solutions for this. – Pär Nils Amsen Jan 20 '17 at 16:26
  • Did not work until I replaced `SpannableStringBuilder strBuilder = new...` with `Spannable strBuilder = (Spannable) sequence;` – jL4 Mar 06 '18 at 14:15
  • 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. – Aleksandr Urzhumtcev Aug 16 '19 at 08:59
  • If you want to change the highlight color of the link, set a color using `android:textColorHighlight="yourColor"` on your `TextView` – artenson.art98 Aug 21 '21 at 08:18
  • This code works fine even when setting android:textIsSelectable="true" in xml. Thank you very much! – Anh Duy Mar 29 '22 at 02:07
  • @Zane Claes Perfect – Akhilesh Mani Jan 28 '23 at 08:01
47

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")
}
Sebastian
  • 776
  • 1
  • 7
  • 7
  • 2
    Awesome!_______ – Valentin Yuryev Aug 12 '20 at 11:51
  • 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) – Damanpreet Singh Mar 23 '21 at 09:09
  • Add Selection.setSelection(text as Spannable, 0) before onClicked?.invoke(it.url) for to avoid the text stays selected after click – Daniel Valencia Jul 26 '22 at 23:43
  • 1
    Every time I return to your solution after trying some others. :-) – CoolMind Dec 14 '22 at 08:18
  • 2
    Is there any way to implement it that we get the click as well as the redirection to browser. Since we are removing the old URLSpan and adding a clickable span in it's place the functionality for it to redirect to the browser on clicking it is lost. – Kartikeya_M Jan 18 '23 at 08:31
25

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);
onexf
  • 3,674
  • 3
  • 22
  • 36
ademar111190
  • 14,215
  • 14
  • 85
  • 114
  • 4
    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? – lightsaber Jan 09 '16 at 12:59
  • @StarWars you can use (StateList)[http://developer.android.com/intl/pt-br/guide/topics/resources/drawable-resource.html#StateList] in pure android, but with HTML I don't know. – ademar111190 Jan 09 '16 at 15:45
  • this working only for hyperlinks. If text contain email, phone number then not clickable. – Niroshan May 27 '21 at 08:43
  • Give this man an award! – Kannan_SJD Apr 28 '22 at 13:44
23

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());
onexf
  • 3,674
  • 3
  • 22
  • 36
Dinesh Anuruddha
  • 7,137
  • 6
  • 32
  • 45
  • 1
    Great! I've modified this for my case. I'll edit my post to include the code. – Zane Claes Sep 14 '12 at 06:08
  • is there a similar solution that can be used inside xml ? – android developer Dec 16 '12 at 12:23
  • 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 Nov 14 '13 at 15:49
  • 1
    I used this logic, however had to replace the UnderlineSpan with URLSpan.Also needed to remove the old spans from the SpannableStringBuilder. – Ray Jan 15 '14 at 02:33
  • 5
    Whats is variable 'd' here ? – Salman Khan Aug 20 '14 at 09:36
  • 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() – Neela Feb 17 '20 at 09:41
18

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

Jordi
  • 616
  • 2
  • 9
  • 16
  • 1
    Hi is there any way to intercept the intent fired at the time of click of a link – Manmohan Soni Jun 27 '18 at 13:26
  • 1
    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 ^^ – Jordi Jan 03 '19 at 09:29
14

Solution

I have implemented a small class with the help of which you can handle long clicks on TextView itself and Taps on the links in the TextView.

Layout

TextView android:id="@+id/text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:autoLink="all"/>

TextViewClickMovement.java

import android.content.Context;
import android.text.Layout;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.Patterns;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.TextView;

public class TextViewClickMovement extends LinkMovementMethod {

    private final String TAG = TextViewClickMovement.class.getSimpleName();

    private final OnTextViewClickMovementListener mListener;
    private final GestureDetector                 mGestureDetector;
    private TextView                              mWidget;
    private Spannable                             mBuffer;

    public enum LinkType {

        /** Indicates that phone link was clicked */
        PHONE,

        /** Identifies that URL was clicked */
        WEB_URL,

        /** Identifies that Email Address was clicked */
        EMAIL_ADDRESS,

        /** Indicates that none of above mentioned were clicked */
        NONE
    }

    /**
     * Interface used to handle Long clicks on the {@link TextView} and taps
     * on the phone, web, mail links inside of {@link TextView}.
     */
    public interface OnTextViewClickMovementListener {

        /**
         * This method will be invoked when user press and hold
         * finger on the {@link TextView}
         *
         * @param linkText Text which contains link on which user presses.
         * @param linkType Type of the link can be one of {@link LinkType} enumeration
         */
        void onLinkClicked(final String linkText, final LinkType linkType);

        /**
         *
         * @param text Whole text of {@link TextView}
         */
        void onLongClick(final String text);
    }


    public TextViewClickMovement(final OnTextViewClickMovementListener listener, final Context context) {
        mListener        = listener;
        mGestureDetector = new GestureDetector(context, new SimpleOnGestureListener());
    }

    @Override
    public boolean onTouchEvent(final TextView widget, final Spannable buffer, final MotionEvent event) {

        mWidget = widget;
        mBuffer = buffer;
        mGestureDetector.onTouchEvent(event);

        return false;
    }

    /**
     * Detects various gestures and events.
     * Notify users when a particular motion event has occurred.
     */
    class SimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onDown(MotionEvent event) {
            // Notified when a tap occurs.
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            // Notified when a long press occurs.
            final String text = mBuffer.toString();

            if (mListener != null) {
                Log.d(TAG, "----> Long Click Occurs on TextView with ID: " + mWidget.getId() + "\n" +
                                  "Text: " + text + "\n<----");

                mListener.onLongClick(text);
            }
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent event) {
            // Notified when tap occurs.
            final String linkText = getLinkText(mWidget, mBuffer, event);

            LinkType linkType = LinkType.NONE;

            if (Patterns.PHONE.matcher(linkText).matches()) {
                linkType = LinkType.PHONE;
            }
            else if (Patterns.WEB_URL.matcher(linkText).matches()) {
                linkType = LinkType.WEB_URL;
            }
            else if (Patterns.EMAIL_ADDRESS.matcher(linkText).matches()) {
                linkType = LinkType.EMAIL_ADDRESS;
            }

            if (mListener != null) {
                Log.d(TAG, "----> Tap Occurs on TextView with ID: " + mWidget.getId() + "\n" +
                                  "Link Text: " + linkText + "\n" +
                                  "Link Type: " + linkType + "\n<----");

                mListener.onLinkClicked(linkText, linkType);
            }

            return false;
        }

        private String getLinkText(final TextView widget, final Spannable buffer, final MotionEvent event) {

            int x = (int) event.getX();
            int y = (int) event.getY();

            x -= widget.getTotalPaddingLeft();
            y -= widget.getTotalPaddingTop();

            x += widget.getScrollX();
            y += widget.getScrollY();

            Layout layout = widget.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);

            ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

            if (link.length != 0) {
                return buffer.subSequence(buffer.getSpanStart(link[0]),
                        buffer.getSpanEnd(link[0])).toString();
            }

            return "";
        }
    }
}

Usage

String str_links = "<a href='http://google.com'>Google</a><br /><a href='http://facebook.com'>Facebook</a>";
text_view.setText( Html.fromHtml( str_links ) );
text_view.setMovementMethod(new TextViewClickMovement(this, context));

Links

Hope this helops! You can find code here.

  • Please, check your code again, from line `text_view.setMovementMethod(new TextViewClickMovement(this, context));` ; Android Studio is complaining that `context` could not be resolved. – X09 May 14 '16 at 11:34
  • If you copy source code from bitbucket, you should change place of context and listener like this text_view.setMovementMethod(new TextViewClickMovement( context. this)); –  May 16 '16 at 06:48
  • That'll be parsing two context for the parameters. Didn't work Sir. Though the accepted answer is working for me now – X09 May 16 '16 at 07:15
  • Thank you for your answer sir, best one out there for this kind, thank you! – Vulovic Vukasin Feb 14 '17 at 21:06
9

A way cleaner and better solution, using native's Linkify library.

Example:

Linkify.addLinks(mTextView, Linkify.ALL);
QED
  • 9,803
  • 7
  • 50
  • 87
Fidel Montesino
  • 337
  • 3
  • 7
8

If you're using Kotlin, I wrote a simple extension for this case:

/**
 * Enables click support for a TextView from a [fullText] String, which one containing one or multiple URLs.
 * The [callback] will be called when a click is triggered.
 */
fun TextView.setTextWithLinkSupport(
    fullText: String,
    callback: (String) -> Unit
) {
    val spannable = SpannableString(fullText)
    val matcher = Patterns.WEB_URL.matcher(spannable)
    while (matcher.find()) {
        val url = spannable.toString().substring(matcher.start(), matcher.end())
        val urlSpan = object : URLSpan(fullText) {
            override fun onClick(widget: View) {
                callback(url)
            }
        }
        spannable.setSpan(urlSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    }
    text = spannable
    movementMethod = LinkMovementMethod.getInstance() // Make link clickable
}

Usage:

yourTextView.setTextWithLinkSupport("click on me: https://www.google.fr") {
   Log.e("URL is $it")
}
Phil
  • 4,730
  • 1
  • 41
  • 39
  • You can omit `fullText` and use `text.toString()` instead. It doesn't work, because clicking on a link opens a browser, not calling `callback(url)`, – CoolMind Dec 14 '22 at 08:12
1

An alternative, imho way simpler approach (for lazy developers like myself ;)

abstract class LinkAwareActivity : AppCompatActivity() {

    override fun startActivity(intent: Intent?) {
        if(Intent.ACTION_VIEW.equals(intent?.action) && onViewLink(intent?.data.toString(), intent)){
            return
        }

        super.startActivity(intent)
    }

    // return true to consume the link (meaning to NOT call super.startActivity(intent))
    abstract fun onViewLink(url: String?, intent: Intent?): Boolean 
}

If required, you could also check for scheme / mimetype of the intent

joe1806772
  • 506
  • 6
  • 20
1

You can do it more neatly using a simple library named Better-Link-Movement-Method.

    TextView mTvUrl=findViewById(R.id.my_tv_url);
    mTvUrl.setMovementMethod(BetterLinkMovementMethod.newInstance().setOnLinkClickListener((textView, url) -> {
              
                    if (Patterns.WEB_URL.matcher(url).matches()) {
                        //An web url is detected 
                        return true;
                    }
                    else if(Patterns.PHONE.matcher(url).matches()){
                        //A phone number is detected 
                        return true;
                    }
                    else if(Patterns.EMAIL_ADDRESS.matcher(url).matches()){
                        //An email address is detected 
                        return true;
                    }
    
                return false;
            }));
Gk Mohammad Emon
  • 6,084
  • 3
  • 42
  • 42
0

Im using only textView, and set span for url and handle click.

I found very elegant solution here, without linkify - according to that I know which part of string I want to linkify

handle textview link click in my android app

in kotlin:

fun linkify(view: TextView, url: String, context: Context) {

    val text = view.text
    val string = text.toString()
    val span = ClickSpan(object : ClickSpan.OnClickListener {
        override fun onClick() {
            // handle your click
        }
    })

    val start = string.indexOf(url)
    val end = start + url.length
    if (start == -1) return

    if (text is Spannable) {
        text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        text.setSpan(ForegroundColorSpan(ContextCompat.getColor(context, R.color.orange)),
                start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    } else {
        val s = SpannableString.valueOf(text)
        s.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        s.setSpan(ForegroundColorSpan(ContextCompat.getColor(context, R.color.orange)),
                start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        view.text = s
    }

    val m = view.movementMethod
    if (m == null || m !is LinkMovementMethod) {
        view.movementMethod = LinkMovementMethod.getInstance()
    }
}

class ClickSpan(private val mListener: OnClickListener) : ClickableSpan() {

    override fun onClick(widget: View) {
        mListener.onClick()
    }

    interface OnClickListener {
        fun onClick()
    }
}

and usage: linkify(yourTextView, urlString, context)

Peter
  • 260
  • 5
  • 3
0

This page solved my problem, but I had to figure something out myself. I was using android string resources to set the text of the TextView and obviously, they returned a CharSequence that has a link in between the text.

These were the resources:

<string name="license_agreement">By registering, you agree with our <b><ahref="www.privacy-options.com">Privacy Policy</a></b> and <b><a href="www.terms-and-conditions.com">Terms and Conditions</a></b></string>

<string name="sign_now">Already have an account? <b><a href="@login_page">Login</a></b></string>

I made changes to one of the code suggested. The code:

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // ...

        // Make Licence agreement statements and login text clickable links
        setLinkOnText(binding.txtLcAgree);
        setLinkOnText(binding.signNow);

    }

    private void detectLinkClick(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 links retrieved from span.getURL(), to handle link click...
            String clickedUrl = span.getURL();
            switch (clickedUrl) {
                case "@login_page":
                    startActivity(new Intent(RegistrationActivity.this, LoginActivity.class));
                    break;
                case "http://www.privacy-options.com":
                    Uri link1 = Uri.parse("http://www.privacy-options.com");
                    startActivity(new Intent(Intent.ACTION_VIEW, link1));
                    break;
                case "http://www.terms-and-conditions.com":
                    Uri link2 = Uri.parse("http://www.terms-and-conditions.com");
                    startActivity(new Intent(Intent.ACTION_VIEW, link2));
                    break;
                default:
                    Log.w(getClass().getSimpleName(), "No action for this");

            }
        }
    };

    strBuilder.setSpan(clickable, start, end, flags);
    strBuilder.removeSpan(span);
}

    protected void setLinkOnText(TextView text) {
        CharSequence sequence = text.getText();
        SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
        URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);
        for (URLSpan span : urls) {
            detectLinkClick(strBuilder, span);
        }
        text.setText(strBuilder);
        text.setMovementMethod(LinkMovementMethod.getInstance());
    }

The links retrieved from span.getUrl() was the initial link I set in the string resource. And since the text in the TextView was already in link format, I just simply used that text in the SpannableStringBuilder.

Noah
  • 567
  • 5
  • 21
0

For The amazing answer by Zane Claes on top. Simply add the below code before calling strBuilder.getSpans() works great for me.

Linkify.addLinks(strBuilder, Linkify.ALL)
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
  • Please use the "Share" link to refer to an existing answer. Because "on top" does not work, this, your post, is the topmost answer for me. Also please explain why adding the shown code to the referenced solution is necessary. What is the benefit of doing that? What is the disadvantage of leaving that out? You do intend to contribute an additional insight to this question, aren't you? – Yunnosch Aug 20 '22 at 14:48
0

You need to create a class that extends LinkMovementMethod and override the onTouchEvent() function. I duplicated some code from LinkMovementMethod's method to get the link.

@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
                            MotionEvent event)
{
    int action = event.getAction();

    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        x -= widget.getTotalPaddingLeft();
        y -= widget.getTotalPaddingTop();

        x += widget.getScrollX();
        y += widget.getScrollY();

        Layout layout = widget.getLayout();
        int line = layout.getLineForVertical(y);
        int off = layout.getOffsetForHorizontal(line, x);

        ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);

        if (links.length != 0) {
            String url = ((URLSpan) links[0]).getURL());
        } else {
            return super.onTouchEvent(widget, buffer, event);
        }
    }
    return super.onTouchEvent(widget, buffer, event);
}