Deveti Putnik Deveti Putnik - 2 months ago 14
Android Question

TextView catch clicking on link when autoLink is set to "web"

I have TextView in my layout with

autoLink='web'
and when I click on it, internal internet browser is opened with URL that is provided as a text in that TextView and everything is fine. What I wish is to do something more with it, i.e. beside opening that URL in browser I want to do some additional actions. What is the least painful way to do it?

Best,

Deveti

Answer

Actually,

I solved this by abandoning autolLink='web' and by parsing html url, creating SpannableString and setting additional spans.

So, let's say that I want to do the following:

  • if user clicks on the part of the text of textView which is not url, method someMethod() should be called
  • if user clicks on the part of the text of textView which is url, method someMethod() should be called and browser should be openned with given URL

So, first we have the content which is in the variable message and method String extractFirstLink(String text) will be used to get the url.

private static String extractFirstLink(String text) {
    Matcher m = Patterns.WEB_URL.matcher(text);
    return m.find() ? m.group() : null;
}

Note: in my case I know that I will have only one url, but if you can expect more than one url, this solution (found somewhere on StackOverflow, can't find it right now) will do the work:

public static String[] extractLinks(String text) {
    List<String> links = new ArrayList<String>();
    Matcher m = Patterns.WEB_URL.matcher(text);
    while (m.find()) {
        String url = m.group();
        links.add(url);
    }
    return links.toArray(new String[links.size()]);
}

So, here is the code:

int contentTextColor = ...; // color of non-url text in textView
int urlTextColor = ...;
String message = notification.getMessage(); // content stored
final String foundLink = extractFirstLink(message);
SpannableString styledString = new SpannableString(message);

if (foundLink != null) {
    int startPosition = message.indexOf(foundLink);
    int endPosition = startPosition + foundLink.length();
    styledString.setSpan(new ForegroundColorSpan(urlTextColor), startPosition, endPosition, 0);

    ClickableSpan clickableURLSpan = new ClickableSpan() {
        @Override
        public void onClick(View widget) {
            someMethod();
            Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(foundLink));
            widget.getContext().startActivity(browserIntent);
        }
    };

    ClickableSpan clickableNonURLSpan1 = new ClickableSpan() {
        @Override
        public void onClick(View view) {
            someMethod();
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            ds.setUnderlineText(false);
        }
    };
    ClickableSpan clickableNonURLSpan2 = new ClickableSpan() {
        @Override
        public void onClick(View view) {
            someMethod();
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            ds.setUnderlineText(false);
        }
    };
    styledString.setSpan(clickableURLSpan, startPosition, endPosition, 0);
    if (startPosition > 0) {
        styledString.setSpan(clickableNonURLSpan1, 0, startPosition - 1, 0);
        styledString.setSpan(new ForegroundColorSpan(contentTextColor), 0, startPosition -1, 0);
    }
    if (endPosition < message.length() -1) {
        styledString.setSpan(clickableNonURLSpan2, endPosition + 1, message.length(),  0);
        styledString.setSpan(new ForegroundColorSpan(contentTextColor), endPosition + 1, message.length(),  0);
    }

    txtView.setMovementMethod(LinkMovementMethod.getInstance());
}

txtView.setText(styledString);

There are some interesting things here. For example, when I wish to make span responsive to click, I have to use ClickableSpan, but! I must handle situation where I have format text1-url-text2. So, since all these three parts should be ClickableSpans and text1 and text2 should not have underline, which is the default behaviour of it, I had to override updateDrawState method in my implementation of ClickableSpan for text1 and text2.

There is another catch, I had to create two implementation of ClickableSpan interface in order to have to work as intended. That's why I created clickableNonURLSpan1 and clickableNonURLSpan2.