18

Having the textView with autoLinkMask set to Linkify.ALL, i'm able to open the links and the browser shows the web-page.

I need to call another activity that will do it with it's webView not leaving the application.

Important notes:

  • changing the textView content is not an option, i need to have the links displayed as they are with the schemes they have,
  • there's lot of text in the textView, not only the link.

I looked through movementMethod and IntentFilters, might miss something but looks like it can't help.

So, any option to intercept the touched link in the TextView to do something with it not opening the browser ?

If you want to mention this SO question, please give some arguments why cause it doesn't seem to solve the same problem as I have.

Community
  • 1
  • 1
A-Live
  • 8,904
  • 2
  • 39
  • 74
  • 1
    The SO question you linked to has the guts of a right answer: replace the `URLSpan` objects generated by `Linkify` with your own custom `ClickableSpan` that does what you want. – CommonsWare Jul 10 '12 at 12:46
  • you want to open link in webview of another activity on text click?? – AkashG Jul 10 '12 at 12:52
  • @AkashG exactly, i want to get the touched url and use it in another activity to show at the web view. Will keep the application style solid, and i don't really want to be blamed instead of the users themselves with the custom browser configured as they like to. – A-Live Jul 10 '12 at 13:17
  • @CommonsWare yes it works, the weakness is that i'll have to find manually all the links and mark them as clickable and the solution must be improved to pass the clicked text to `onClick` event, what i like though is that it also makes possible to linkify the tags! Please rewrite your comment to be an answer, sir, and thank you a lot. – A-Live Jul 10 '12 at 15:39

5 Answers5

26

Step #1: Create your own subclass of ClickableSpan that does what you want in its onClick() method (e.g., called YourCustomClickableSpan)

Step #2: Run a bulk conversion of all of the URLSpan objects to be YourCustomClickableSpan objects. I have a utility class for this:

public class RichTextUtils {
    public static <A extends CharacterStyle, B extends CharacterStyle> Spannable replaceAll(Spanned original,
    Class<A> sourceType,
    SpanConverter<A, B> converter) {
        SpannableString result=new SpannableString(original);
        A[] spans=result.getSpans(0, result.length(), sourceType);

        for (A span : spans) {
            int start=result.getSpanStart(span);
            int end=result.getSpanEnd(span);
            int flags=result.getSpanFlags(span);

            result.removeSpan(span);
            result.setSpan(converter.convert(span), start, end, flags);
        }

        return(result);
    }

    public interface SpanConverter<A extends CharacterStyle, B extends CharacterStyle> {
        B convert(A span);
    }
}

You would use it like this:

yourTextView.setText(RichTextUtils.replaceAll((Spanned)yourTextView.getText(),
                                             URLSpan.class,
                                             new URLSpanConverter()));

with a custom URLSpanConverter like this:

class URLSpanConverter
      implements
      RichTextUtils.SpanConverter<URLSpan, YourCustomClickableSpan> {
    @Override
    public URLSpan convert(URLSpan span) {
      return(new YourCustomClickableSpan(span.getURL()));
    }
  }

to convert all URLSpan objects to YourCustomClickableSpan objects.

hasanghaforian
  • 13,858
  • 11
  • 76
  • 167
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • That makes the solution so complete, thanks again. I also find it to be better then another solution discussed in the comments above, looks super-cool in fact. – A-Live Jul 10 '12 at 16:27
  • @A-Live I followed this solution but the links in textview are not clickable. I tried toggling android:linksClickable & tried different values for android:autoLink. Thoughts? – Ravi May 22 '14 at 03:31
  • 1
    @Ravi I don't know what you mean exactly or what is the best way to help you, creating a separate question and putting as much details as possible there must be a way to go. – A-Live May 22 '14 at 11:19
  • This solution not only works in regular usage but also during talkback in accessibility mode :) – Abhinav Tyagi Jul 10 '20 at 19:12
3

I make @CommonsWare's code more elegant and clear by adding Click Listener which can be added directly into the same method which replace URL Spans.

  1. Define YourCustomClickableSpan Class.

     public static class YourCustomClickableSpan extends ClickableSpan {
    
        private String url;
        private OnClickListener mListener;
    
        public YourCustomClickableSpan(String url, OnClickListener mListener) {
            this.url = url;
            this.mListener = mListener;
        }
    
        @Override
        public void onClick(View widget) {
            if (mListener != null) mListener.onClick(url);
        }
    
        public interface OnClickListener {
            void onClick(String url);
        }
    }
    
  2. Define RichTextUtils Class which will manipulate the Spanned text of your TextView.

    public static class RichTextUtils {
    
        public static <A extends CharacterStyle, B extends CharacterStyle> Spannable replaceAll (
              Spanned original,
              Class<A> sourceType,
              SpanConverter<A, B> converter,
              final ClickSpan.OnClickListener listener) {
    
                  SpannableString result = new SpannableString(original);
                  A[] spans = result.getSpans(0, result.length(), sourceType);
    
                  for (A span : spans) {
                      int start = result.getSpanStart(span);
                      int end = result.getSpanEnd(span);
                      int flags = result.getSpanFlags(span);
    
                      result.removeSpan(span);
                      result.setSpan(converter.convert(span, listener), start, end, flags);
                  }
    
                  return (result);
              }
    
              public interface SpanConverter<A extends CharacterStyle, B extends CharacterStyle> {
                  B convert(A span, ClickSpan.OnClickListener listener);
    
         }
    }
    
  3. Define URLSpanConverter class which will do the actual code of replacement URLSpan with Your Custom Span.

        public static class URLSpanConverter
            implements RichTextUtils.SpanConverter<URLSpan, ClickSpan> {
    
    
            @Override
            public ClickSpan convert(URLSpan span, ClickSpan.OnClickListener listener) {
                return (new ClickSpan(span.getURL(), listener));
            }
       }
    
  4. Usage

    TextView textView = ((TextView) this.findViewById(R.id.your_id));
    textView.setText("your_text");
    Linkify.addLinks(contentView, Linkify.ALL);
    
    Spannable formattedContent = UIUtils.RichTextUtils.replaceAll((Spanned)textView.getText(), URLSpan.class, new UIUtils.URLSpanConverter(), new UIUtils.ClickSpan.OnClickListener() {
    
        @Override
        public void onClick(String url) {
            // Call here your Activity
        }
    });
    textView.setText(formattedContent);
    

Note that I defined all classes here as static so you can put it directly info your Util class and then reference it easily from any place.

rupinderjeet
  • 2,984
  • 30
  • 54
Ayman Mahgoub
  • 4,152
  • 1
  • 30
  • 27
  • 2
    Thanks, hopefully the answer will help somebody to better understand the flow and not simply copy-paste-forget, the latter is why I like the way CommonsWare wisely left out the full implementation of the `ClickableSpan` subclass. – A-Live Feb 02 '15 at 07:43
2

Just to share an alternative solution using Textoo that I just created:

    TextView yourTextView = Textoo
        .config((TextView) findViewById(R.id.your_text_view))
        .addLinksHandler(new LinksHandler() {
            @Override
            public boolean onClick(View view, String url) {
                if (showInMyWebView(url)) {
                    //
                    // Your custom handling here
                    //
                    return true;  // event handled
                } else {
                    return false; // continue default processing i.e. launch browser app to display link
                }
            }
        })
        .apply();

Under the hood the library replace URLSpan's in the TextView with custom ClickableSpan implementation that dispatch click events to the user supplied LinksHandler(s). The mechanism is very similar to solution from @commonsWare. Just package in a higher level API to make it easier to use.

PH88
  • 1,796
  • 12
  • 12
0

I think David Hedlund's answer to himself is the way to go. In my case I had a TextView containing SMS content form user's inbox and I wanted to handle the fallowing behaviour:

  1. If a WEB URL is found then linkify it and handle the click within the application.
  2. If PHONE NUMBER is found, then linkify it and handle the click showing a picker to let the user choose if call the number or add it to the address-book (all within the application)

To achieve this I used the linked answer in this way:

TextView tvBody = (TextView)findViewById(R.id.tvBody);
tvBody.setText(messageContentString);

// now linkify it with patterns and scheme
Linkify.addLinks(tvBody, Patterns.WEB_URL, "com.my.package.web:");
Linkify.addLinks(tvBody, Patterns.PHONE, "com.my.package.tel:");

Now in the manifest:

<activity
    android:name=".WebViewActivity"
    android:label="@string/web_view_activity_label">
    <intent-filter>
        <category android:name="android.intent.category.DEFAULT" />
        <action android:name="android.intent.action.VIEW"/>
        <data android:scheme="com.duckma.phonotto.web"/>
    </intent-filter>
</activity>
...
<activity
    android:name=".DialerActivity"
    android:label="@string/dialer_activity_label">
    <intent-filter>
        <category android:name="android.intent.category.DEFAULT" />
        <action android:name="android.intent.action.VIEW"/>
        <data android:scheme="com.duckma.phonotto.tel"/>
    </intent-filter>
</activity>
<activity
    android:name=".AddressBook"
    android:label="@string/address_book_activity_label">
    <intent-filter>
        <category android:name="android.intent.category.DEFAULT" />
        <action android:name="android.intent.action.VIEW"/>
        <data android:scheme="com.duckma.phonotto.tel"/>
    </intent-filter>
</activity>

And it works just fine for me, when user clicks a phone number the picker will show up with the dialer/address book choice. In the called activities use getIntent().getData() to find the passed Url with the data in it.

Community
  • 1
  • 1
Carlo Moretti
  • 2,213
  • 2
  • 27
  • 41
  • 1
    Although it might be an advantage for somebody, the way we always start an activity is a limiting factor here whereas custom spans let us process links any way we want. Another issue is that we need to re-invent a wheel with `Linkify` alternative, and considering that the original text might contain html-recognizable parts we'd need quite an elaborate logic not to let `Html.fromHtml` to break it. – A-Live Feb 23 '15 at 13:52
  • I see, in fact it fits better my case where I use it for SMS content which rarely would include html tags. – Carlo Moretti Feb 23 '15 at 14:02
-3

Than do this:

        TextView textView=(TextView) findViewById(R.id.link);
        textView.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {
                Intent intent=new Intent(YourActivityName.this,WebActivity.class);
                startActivity(intent);
            }
        });

onCreate of WebActivity Class:

WebView webView=(WebView) findViewById(R.id.web);
webView.loadUrl("http://www.google.co.in");

Add this under textview in xml:

android:clickable="true"
AkashG
  • 7,868
  • 3
  • 28
  • 43