40

I am using a TextView for which I have set autolink="web" property in XML file. I have also implemented the onClickListener for this TextView. The problem is, when the text in TextView contains a hyperlink, and if I touch that link, the link opens in browser but simultaneously the onClickListener triggers too. I don't want that.

What I want is, if I touch the hyperlink the clickListener should not fire. It should only fire if I touch the part of the text that is not hyperlinked. Any suggestion?

Vickyexpert
  • 3,147
  • 5
  • 21
  • 34
Adnan
  • 2,986
  • 7
  • 43
  • 63

9 Answers9

25

You can achieve this using a work around in getSelectionStart() and getSelectionEnd() functions of the Textview class,

tv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ClassroomLog.log(TAG, "Textview Click listener ");
        if (tv.getSelectionStart() == -1 && tv.getSelectionEnd() == -1) {
            //This condition will satisfy only when it is not an autolinked text
            //Fired only when you touch the part of the text that is not hyperlinked 
        }
    }
});

It may be a late reply, but may be useful to those who are searching for a solution.

WarrenFaith
  • 57,492
  • 25
  • 134
  • 150
binary
  • 1,364
  • 1
  • 15
  • 20
  • 5
    if i click outside the text as well as links in textview, it still opens link view, which i want only to happen when clicked on link. – zoya ali Oct 19 '13 at 02:45
  • Dude life save on this one! Thanks mine was the opposite I needed to do some pre-processing before it went to hyperlink so just changed the -1 to > 0 and viola it worked – JPM Oct 24 '14 at 21:46
  • look at this if it didn't works http://stackoverflow.com/a/41603171/5873832 – Dasser Basyouni Jan 12 '17 at 00:27
11

one of the @CommonsWare post helps to intercept autolink OnClick event.

private void fixTextView(TextView tv) {
    SpannableString current = (SpannableString) tv.getText();
    URLSpan[] spans =
            current.getSpans(0, current.length(), URLSpan.class);

    for (URLSpan span : spans) {
        int start = current.getSpanStart(span);
        int end = current.getSpanEnd(span);

        current.removeSpan(span);
        current.setSpan(new DefensiveURLSpan(span.getURL()), start, end,
                0);
    }
}

public static class DefensiveURLSpan extends URLSpan {
    private String mUrl;

    public DefensiveURLSpan(String url) {
        super(url);
        mUrl = url;
    }

    @Override
    public void onClick(View widget) {
        // openInWebView(widget.getContext(), mUrl); // intercept click event and do something.
        // super.onClick(widget); // or it will do as it is.
    }
}

Apply above code simply as below. It will go through all linkable texts and replace click events to above event handler.

fixTextView(textViewContent);
Youngjae
  • 24,352
  • 18
  • 113
  • 198
  • That post made all the difference. Very important note at the end: "Note that you do not want to call setText() on the TextView, thinking that you would be replacing the text with the modified version. You are modifying the TextView’s text in place in this fixTextView() method, and therefore setText() is not necessary. Worse, if you are using android:autoLink, setText() would cause Android go back through and add URLSpans again." – trans Jul 19 '16 at 17:56
  • Excellent, just what I needed – Myke Dev Jul 13 '17 at 15:34
4

You can set the property android:linksClickable="false" in your TextView, in conjuction with android:autoLink="web"; this makes the links visible, but not clickable.

moondroid
  • 1,730
  • 17
  • 20
2

if you wish, you can use the next code which allows to customize the clickable links within the string ( based on this post ) :

usage:

final TextView textView = (TextView) findViewById(R.id.textView);
final Spanned text = Html.fromHtml(getString(...));
textView.setText(text);
textView.setMovementMethod(new LinkMovementMethodExt());

LinkMovementMethodExt.java

public class LinkMovementMethodExt extends LinkMovementMethod {
    private static LinkMovementMethod sInstance;

    public static MovementMethod getInstance() {
        if (sInstance == null)
            sInstance = new LinkMovementMethodExt();
        return sInstance;
    }

    @Override
    public boolean onTouchEvent(final TextView widget, final Spannable buffer, final MotionEvent event) {
        final int action = event.getAction();
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            final int x = (int) event.getX() - widget.getTotalPaddingLeft() + widget.getScrollX();
            final int y = (int) event.getY() - widget.getTotalPaddingTop() + widget.getScrollY();
            final Layout layout = widget.getLayout();
            final int line = layout.getLineForVertical(y);
            final int off = layout.getOffsetForHorizontal(line, x);
            final ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
            if (link.length != 0) {
                //do something with the clicked item...
                return true;
            }
        }
        return super.onTouchEvent(widget, buffer, event);
    }

}
Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270
2

Kotlin version:

Similar to older answers in Java. Simply:

  1. In Layout Editor/XML, add the types of things you'd like to hyperlink via the autoLink property.

    <TextView
        ...
        android:autoLink="web|phone|email" />
    
  2. Add an onClickListener to your TextView in Kotlin code to handle clicks on the plain text part. Check to make sure the person didn't click on a link by checking selectionStart and selectionEnd.

    binding.messageText.setOnClickListener { view ->
        if (binding.messageText.selectionStart == -1 && binding.messageText.selectionEnd == -1) {
            // do whatever you want when they click on the plain text part
        }
    }
    
wildcat12
  • 975
  • 6
  • 13
  • This method works if the text we are clicking on doesn't contain any links. If there's a link in some part of that text, The whole paragraph won't respond to clicklistener. Is there a way to improve this? – Hamed Apr 24 '22 at 18:28
1
private void fixTextView(TextView tv) {
    SpannableString current = (SpannableString) tv.getText();
    URLSpan[] spans =
            current.getSpans(0, current.length(), URLSpan.class);

    for (URLSpan span : spans) {
        int start = current.getSpanStart(span);
        int end = current.getSpanEnd(span);

        current.removeSpan(span);
        current.setSpan(new DefensiveURLSpan(span.getURL()), start, end,
                0);
    }
}

public static class DefensiveURLSpan extends URLSpan {

    public final static Parcelable.Creator<DefensiveURLSpan> CREATOR =
            new Parcelable.Creator<DefensiveURLSpan>() {

        @Override
        public DefensiveURLSpan createFromParcel(Parcel source) {
            return new DefensiveURLSpan(source.readString());
        }

        @Override
        public DefensiveURLSpan[] newArray(int size) {
            return new DefensiveURLSpan[size];
        }

    };

private String mUrl;

public DefensiveURLSpan(String url) {
    super(url);
    mUrl = url;
}

    @Override
    public void onClick(View widget) {
        // openInWebView(widget.getContext(), mUrl); // intercept click event and do something.
        // super.onClick(widget); // or it will do as it is.
    }
}

You would then call fixTextView(textViewContent); on the view after it is declared (via inflation or findViewById) or added to the window (via addView)

This includes the missing requirement to set a CREATOR when extending a Parcelable.

It was proposed as an edit, but rejected. Unfortunately, now future users will have to find out the original one is incomplete first. Nice one, reviewers!

Abandoned Cart
  • 4,512
  • 1
  • 34
  • 41
  • why do you think it's needed? Answer, which you refer to, works without `CREATOR` field – wzieba Feb 16 '18 at 12:00
  • @wzieba it depends on your target API, IDE, and error settings. If you want to use a truncated version and your particular configuration allows it, then by all means. Just be aware that it may stop working at any time. – Abandoned Cart Feb 16 '18 at 22:34
  • I still don't get how target API and in particular IDE could be a cause of any error in this code. It would be very useful if you would post any example to your answer that `CREATOR` field has sense. – wzieba Feb 20 '18 at 08:14
  • https://s13.postimg.org/gplqkepp3/Screen_Shot_2018-02-20_at_1.52.05_PM.png – Abandoned Cart Feb 20 '18 at 18:57
1

Use textView.getSelectionStart() and textView.getSelectionEnd().If u click any text other than link textView.getSelectionStart() and textView.getSelectionEnd() will be -1 .So by using a if condition in onClickListner you can block the onClick action when link is clicked .

//inside onClickListner

if(textView.getSelectionStart()==-1&&textView.getSlectionEnd==-1){

    //onClick action
}
McDowell
  • 107,573
  • 31
  • 204
  • 267
nila
  • 3,223
  • 4
  • 18
  • 15
0

Just adding

textView.setMovementMethod(CustomLinkMovementMethod.getInstance());

to @binary's answer for those whose the method did not work with them

Dasser Basyouni
  • 3,142
  • 5
  • 26
  • 50
0

Instead of using a onClickListener, you can try this.

private void addLink() {
        tvLink = (TextView) findViewById(R.id.tvInfo2);

        String strURL = UrlLoader.getCodeUrl();

        // Make the url string clicable and take action in its onclick
        SpannableString spanUrl = SpannableString.valueOf(strURL);
        spanUrl.setSpan(new InternalURLSpan(new OnClickListener() {
            public void onClick(View v) {

                //Do Some action
            }
        }), 0, spanUrl.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        tvLink.setText(spanUrl);

        // We probably also want the user to jump to your link by moving the
        // focus (e.g. using the trackball), which we can do by setting the
        // proper movement method:
        MovementMethod m = tvLink.getMovementMethod();
        if ((m == null) || !(m instanceof LinkMovementMethod)) {
            if (tvLink.getLinksClickable()) {
                tvLink.setMovementMethod(LinkMovementMethod.getInstance());
            }
        }
    }

Also in the layout XML file , dont forget to add

<TextView android:layout_width="wrap_content" android:linksClickable="true"
android:layout_height="wrap_content" android:id="@+id/tvInfo2" android:text="@string/url_link" />
Alok Kulkarni
  • 2,153
  • 19
  • 31