Clickable URLs in Android TextViews

Android’s TextView widget can contain clickable URLs. It can easily make web addresses open in the browser, or connect phone numbers with the dialer. All that is amazing compared to the last GUI framework I used, Delphi’s once great VCL).

Unfortunately, both TextView and the Linkify utility it uses basically hardcode URL click handling to Intents, by way of the URLSpans they create. What if we want the link to affect something within your own Activity, say, display a dialog, or enable a filter?

For example, in Autostarts, if the user’s filters cause the application list to be empty, I wanted to display an explanatory message, and provide a quick and easy way for the user to rectify the situation, i.e. lead him towards the filter selection dialog. Making the whole text clickable is hard to get right visually, and I didn’t like the idea of a button too much. A link within the text seemed perfect.

Now, we could just use a custom URL scheme of course, and register our Activity to handle Intents for that scheme, but that seemed much too heavy, if not hacky. Why shouldn’t we be able to just hook up an onClick handler?

As mentioned, URLSpan doesn’t allow us to change the way it handles clicks (it always sends off an Intent), but we can create a subclass:

static class InternalURLSpan extends ClickableSpan {
	OnClickListener mListener;

	public InternalURLSpan(OnClickListener listener) {
		mListener = listener;
	}

	@Override
	public void onClick(View widget) {
		mListener.onClick(widget);
	}
}

That looks pretty decent. Actually using that class it is tougher. There is no way to tell TextView or Linkify to use our custom span. In fact, Linkify actually has a method (applyLink) that would be nearly perfect to override, but declares it final.

So, we end up having to generate the spans manually; note nice, but hey, it works.

SpannableString f = new SpannableString("....")
f.setSpan(new InternalURLSpan(new OnClickListener() {
        public void onClick(View v) {
            showDialog(DIALOG_VIEW_OPTIONS);
        }
    }), x, y, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

We probably also want the user to jump to your link my moving the focus (e.g. using the trackball), which we can do by setting the proper movement method:

MovementMethod m = emptyText.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod)) {
    if (textView.getLinksClickable()) {
        textView.setMovementMethod(LinkMovementMethod.getInstance());
    }
}

13 thoughts on “Clickable URLs in Android TextViews

  1. Hello there i just wanted you to ask that how can this above mentioned method be used in my scenario where my requirement get solved, if I have a string like this – “This is a #test of regular expressions with http://example.com links as used in @twitter. http://twitter.com.”; if this an text in the TextView then I require #test, example.com, @twitter and twitter.com as separate strings and Clickable from Textview and if Clicked I require them to be handled in a custom method for giving my actions over these String, can this is be achieved.

    Like

  2. Yes. You need to use a regex to find the string indices of the substrings you want to link, then concat all the different substrings with the correct Span classes – for example the InternalURLSpan shown above.

    Like

  3. Hello

    your code works perfect. I have just a problem with getting the clicked text.
    On Y Ramesh’s example: “This is a #test of regular expressions with http://example.com links as used in @twitter. http://twitter.com.”;

    How do you get the clicked text? The onClick method has the whole textview.

    Oh and here is my code for wrapping http with spans and a foregroundcolor

    int start = 0;
    int end = 0;
    int offset = 0;
    if(countMatches > 0){
    while(countMatches > 0){
    // getting start and end index of the http occurences
    start = text.indexOf(“http”, offset);
    end = text.indexOf(” “, start);
    offset = end;
    f.setSpan(new InternalUrlSpan(new OnClickListener() {
    @Override
    public void onClick(View v) {
    if(v instanceof TextView){
    System.out.println(((TextView)v).getText());
    }
    }
    }), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    f.setSpan(new ForegroundColorSpan(0xFF3366FF), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

    countMatches–;
    }
    }

    Like

  4. found the solution:

    int selectionStart = textView.getSelectionStart();
    int selectionEnd = textView.getSelectionEnd();

    Like

  5. The onClick event doesn’t seem to work for me. I’m using Android 2.1 and I replaced the showDialog() you’ve used with a Log.v() and the click isn’t logging which implies that the onClick method isn’t being called at all.

    P.S: I skipped the MovementMethod part coz I don’t need it.

    Like

  6. Akash Manohar: You have to set your TextView’s movement method to LinkMovementMethod. Otherwise clickable span won’t work as documentation for ClickableSpan says: “If an object of this type is attached to the text of a TextView with a movement method of LinkMovementMethod, the affected spans of text can be selected. If clicked, the onClick(View) method will be called.”

    Like

  7. how can i pass text of text view from one class to another class text view in one activity on android ( when we clicked selected text then it well be passed to another class text view ) how it is possible

    Like

  8. Why did you have to create a custom subclass of ClickableSpan. Instead you can create a ClickableSpan variable:

    ClickableSpan clickable = new ClickableSpan() {
    				
    				@Override
    				public void onClick(View widget) {
    		            Log.e("select text", "hi");					
    				}
    			};
    SpannableString f = new SpannableString("....") ;
    f.setSpan(clickable,  x, y, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    

    Like

  9. Hey, You can’t set your server’s content in android:text of xml file.
    You have to map it through its id in your activity. then using that you have to use setText() method of TextView in your activity
    Have a nice day 🙂

    Like

  10. Hello,

    The code works fine.
    But I have a problem that the entire textview is selected but only the required part of string is clickable. I dont want the entire textview to have selection.
    How can I disable this.

    Like

Leave a comment