Open TextView links at another activity, not default browser
Question
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.
Solution
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.
OTHER TIPS
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.
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); } }
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); } }
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)); } }
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.
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.
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:
- If a WEB URL is found then linkify it and handle the click within the application.
- 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.
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"