How to process textview for HTML and Linkify - android

I am trying to get a textview to process a hyperlink as well as phone numbers. Say my text is:
"555-555-555, www.google.com, Google!"
If I run Html.fromHtml() on this string, then the TextView shows Google! correctly as a clickable link but not the other two.
If I run Linkify.addLinks(TextView, Linkify.All) on the TextView, then the first two are correctly recognized as a phone number and url, but the html is not processed in the last one.
If I run both of them, then either one or the other is honored, but not both at the same time. (Html.fromHtml will remove the html tags there, but it won't be a link if linkify is called after)
Any ideas on how to get both of these functions to work simultaneously? So all the links are processed correctly? Thanks!
Edit: Also, the text is changed dynamically so I'm not sure how I would be able to go about setting up a Linkify pattern for that.

It's because Html.fromHtml and Linkify.addLinks removes previous spans before processing the text.
Use this code to get it work:
public static Spannable linkifyHtml(String html, int linkifyMask) {
Spanned text = Html.fromHtml(html);
URLSpan[] currentSpans = text.getSpans(0, text.length(), URLSpan.class);
SpannableString buffer = new SpannableString(text);
Linkify.addLinks(buffer, linkifyMask);
for (URLSpan span : currentSpans) {
int end = text.getSpanEnd(span);
int start = text.getSpanStart(span);
buffer.setSpan(span, start, end, 0);
}
return buffer;
}

try to set movement method on your textview instead of using Linkify:
textView.setMovementMethod(LinkMovementMethod.getInstance());

In your TextView's xml layout, you should add the following:
android:autoLink="all"
android:linksClickable="true"
Then you should remove your Linkify code in Java.
It works somehow, but I dont know why. I added a question to see if someone can explain the behavior: Using Linkify.addLinks combine with Html.fromHtml

Related

Can TextView autoLink and setMovementMethod both be made to work

A number of discussions on here going back years related to getting hyperlinks to work in a TextView. The conclusion is that autoLink works for parsing out URLs that are simply embedded in the text, e.g., "go to www.google.com". Then there is
setMovememtMethod(LinkMovementMethod.getInstance());
that will cause actual HTML tags to work, e.g. go to Google. However, using the latter causes autoLink to not work in the same view.
My issue is that I am displaying text that is supplied by a user database, so I have no control over the formatting. In some cases, this text has plaintext links while in others it is entered as HTML tags. Is there any way to get both types of links to work at the same time?
Both plain text links & links with HTML tags will work with the below code
TexView in xml
<TextView
android:id="#+id/txt_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000"
android:textColorLink="#06b" />
Here in activity.java
String text = "this is the link with anchor tag Google. here is the plain text link http://www.google.com";
TextView textView = (TextView) findViewById(R.id.txt_view);
textView.setText(getParsedLinks(text));
textView.setMovementMethod(LinkMovementMethod.getInstance());
instead of using android:autoLink="all" in xml or Linkify.addLinks(textView, Linkify.ALL) use Linkify.addLinks to SpannableString as in below method
SpannableString getParsedLinks(String txt){
Spanned span = Html.fromHtml(txt);
URLSpan[] urlSpans = span.getSpans(0, span.length(), URLSpan.class);
SpannableString s = new SpannableString(span);
Linkify.addLinks(s, Linkify.ALL);
for (URLSpan urlSpan : urlSpans) {
s.setSpan(urlSpan, span.getSpanStart(urlSpan), span.getSpanEnd(urlSpan), 0);
}
return s;
}

AutoLink with hyperLink android

I have a textview which can contain links like https://www.google.com and hyper links with anchor tag Google
Now, I have added the below properties on this textview.
Linkify.addLinks(textview, Linkify.WEB_URLS);
textview.setMovementMethod(LinkMovementMethod.getInstance());
But the links like https://www.google.com these are coming fine in blue and redirecting to the page but anchor tags are not coming in blue and they are not redirecting it.
So, I want to make my textview to render both type of links: direct links and hyper links. How can I do this.
Linkify (the way you've invoked it) only knows to convert things that actually look like web URLs (i.e. they begin with http or https, followed by colon and two slashes, etc. etc).
If you want to convert something else into links, you will have to add some more parameters to Linkify to give it more smarts to convert what you want. You can create a MatchFilter and a TransformFilter then call Linkify.addLinks(TextView text, Pattern p, String scheme, Linkify.MatchFilter matchFilter, Linkify.TransformFilter transformFilter)
But it looks to me like you want to take a word like "Google" and add a link for "https://www.google.com". That's not something that can be scanned. For that, you need to use a SpannableStringBuilder. Your code might look something like this:
String text = "This is a line with Google in it.";
Spannable spannable = new SpannableString(text);
int start = text.indexOf("Google");
int end = start + "Google".length();
URLSpan urlSpan = new URLSpan("https://www.google.com");
spannable.setSpan(urlSpan, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(spannable);
It's mentioned in javadoc of Linkify#addLinks(Spannable, Int) that:
...If the mask is nonzero, it also removes any existing URLSpans attached to the Spannable, to avoid problems if you call it repeatedly on the same text.
Although it is not mentioned in Linkify#addLinks(TextView, Int) which you're using, it appears that they follow the same behavior and existing links (i.e. the 'anchor tags' in your question) would be removed before linkify.
To workaround and preserve existing links ('anchor tags' in your case), you need to backup existing spans (i.e. TextView#getText --> convert to Spanned --> use Spanned#getSpans to list existing links --> use Spanned#getSpanStart and Spanned#getSpanEnd and Spanned#getSpanFlags to retrieve the settings of each)
After linkify, re-add the spans (i.e. TextView#getText --> convert to Spannable --> use Spannable#setSpan to re-add the links --> Set the Spannable back with TextView#setText)
Depending on your case, you might also need to check for overlapping 'anchor tags' and 'linkify links' and adjust accordingly...
As you see, this is quite tedious and complex and error prone to code. To simplify things, I have just incorporate all these into Textoo library for reuse and sharing. With Textoo, you can achieve the same by:
TextView myTextView = Textoo
.config((TextView) findViewById(R.id.view_location_disabled))
.linkifyWebUrls()
.apply();
Textoo will preserve exiting links and linkify all non-overlapping web urls.
//the string to add links to
val htmlString = "This has anchors and urls http://google.com also Google."
//Initial span from HtmlCompat will link anchor tags
val htmlSpan = HtmlCompat.fromHtml(htmlString, HtmlCompat.FROM_HTML_MODE_LEGACY) as Spannable
//save anchor links for later
val anchorTagSpans = htmlSpan.getSpans(0, htmlSpan.length, URLSpan::class.java)
//add first span to TextView
textView.text = htmlSpan
//Linkify will now make urls clickable but overwrite our anchor links
Linkify.addLinks(textView, Linkify.ALL)
textView.movementMethod = LinkMovementMethod.getInstance()
textView.linksClickable = true
//we will add back the anchor links here
val restoreAnchorsSpan = SpannableString(textView.text)
for (span in anchorTagSpans) {
restoreAnchorsSpan.setSpan(span, htmlSpan.getSpanStart(span), htmlSpan.getSpanEnd(span), Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
//all done, set it to the textView
textView.text = restoreAnchorsSpan

Specifying "strikethrough" on a section of TextView text

I have a block of text coming from a webservice, and depending on some tags which I have predefined, I want to style the text before setting it to my TextView. For bold, italics, and underline, I was able to do this easily with the replaceAll command:
PageText = PageText.replaceAll("\\*([a-zA-Z0-9]+)\\*", "<b>$1</b>");
PageText = PageText.replaceAll("=([a-zA-Z0-9]+)=", "<i>$1</i>");
PageText = PageText.replaceAll("_([a-zA-Z0-9]+)_", "<u>$1</u>");
txtPage.setText(Html.fromHtml(PageText), TextView.BufferType.SPANNABLE);
So, to bold a word, surround it with *'s, for italics, surround with _.
But, for strikethrough, Html.fromHtml does not support the "strike" tag, so it can't be done this same way. I've seen examples of using Spannable to set the styling on one section of text, but it requires positional numbers. So, I guess I could loop through the text, searching for - (the tag to represent the strike), then searching for the next one, spanning the text in between, and repeating for all such strings. It will end up being 10 lines of looping code as opposed to 1 for the others, so I'm wondering if there is a more elegant solution out there.
If it is just TextView you can strike through using paint flags
TextView tv=(TextView) v.findViewById(android.R.id.text1);
tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
#Suresh solution works if you want to strikethrough the entire TextView but if you want to strikethrough only some portions of the text then use the code below.
tvMRP.setText(text, TextView.BufferType.SPANNABLE);
Spannable spannable = (Spannable) tvMRP.getText();
spannable.setSpan(new StrikethroughSpan(), 3, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Here text is the text which we want out TextView to display, 3 is the no. of characters (starting from 0) from where the strikethrough will start.
You can do it with a custom TagHandler such as the one on this SO question:
Spanned parsed = Html.fromHtml(PageText, null, new MyHtmlTagHandler());
And the TagHandler implements the methods:
public void handleTag(boolean opening, String tag, Editable output,
XMLReader xmlReader) {
if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
processStrike(opening, output);
}
}
....
Are you sure Html.fromHtml doesn't support <strike>? It's listed in this Commonsware blog post
It looks like is not really supported, at least it does not work on Android 3.1.
#RMS2 if text is small you can split it into two or three separate text views and apply flag only to the one which you want, not perfect for long texts ;(
Most of the applications we work in are going to use text somewhere throughout the project and thankfully, KTX provides some extension functions when it comes to these parts. For text, we essentially have some functions available for the SpannableStringBuilder class.
For example, after instantiating a Builder instance we can use the build methods to append some bold text:
textView.text =buildSpannedString {
strikeThrough {
append(
value ?: ""
)
}
}

Android: Is it possible to have a Spanned with part of it's text focusable within a TextView?

I have this currently.
ClickableSpan span = new ClickableSpan(...){...};
String text = "I am some <b>awesome</b> text";
Spanned spanned = Html.fromHtml(text);
SpannableStringBuilder builder = new SpannableStringBuilder(spanned);
int start = builder.nextSpanTransition(0, builder.length(), StyleSpan.class);
int stop = builder.nextSpanTransition(start, builder.length(), StyleSpan.class);
builder.setSpan(span, start, stop, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
textView.setText(builder);
The TextView renders with the text that has the word "awesome" bolded and underlined (Yay). However in my view, I cannot focus the subregion of text I specified in the clickablespan. I can click on it with a touch event, but I cannot focus it. I am testing this on Android 1.5 + 2.1. I have also tried UrlSpan as well.
I have also tried instead of using a ClickableSpan, to actually attach an onClick listener to the entire block of text but that doesn't give the region focus, just makes clicking easier. Please help
Ok I just figured it out. I originally was looking at the UrlSpan documentation and then just blindly implemented ClickableSpan.
textView.setMovementMethod(LinkMovementMethod.getInstance());
And magically through the powers of this undocumented class ... it works. So basically what I think is going on, is the MovementMethod is a way to supply a textview with a strategy to handle cursor input.

Is there any example about Spanned and Spannable text

I'm struggling with using EditText and Spannable text object, These days, I've read API documents around ten times, even I'm not certain that I understand correctly. So I'm looking for a kind of example which show me how to utilize EditText and Spannable.
Since you don't specify what you can't grasp from the API it's hard to answer your questions (short answer: rewrite your question to a specific questions rather than a general one).
A typical Spannable-example is something like this to turn selected text in an EditText into Italic:
Spannable str = mBodyText.getText();
if(mBodyText.getSelectionEnd() > mBodyText.getSelectionStart())
str.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC),
mBodyText.getSelectionStart(), mBodyText.getSelectionEnd(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
else
str.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC),
mBodyText.getSelectionEnd(),
mBodyText.getSelectionStart(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
This is cut and pasted from something else, so your direct-pastability might have suffered, but it at least shows a working example of a Spannable (in this case a StyleSpan). In the API you can find the other types of Spans (notably ImageSpan, which is a common questions among newly converted droiders).
I'm just starting to try to figure it out too, and it seems unnecessarily tricky.
Here's a working method to add NEW spannable text to an existing view. I wanted to add colored text to a view, and this seemed like the only way to do it.
Though it feels like an ugly hack, you can create a dummy TextView (not shown anywhere) and style the text there, then append that styled text to wherever you want. Credit for it goes to iifuzz at anddev.org. My code looks like so:
spanbuffer = new TextView(context);
spanbuffer.setText(newText, TextView.BufferType.SPANNABLE);
Spannable s = (Spannable) spanbuffer.getText();
s.setSpan(new ForegroundColorSpan(Color.RED), 0, newText.length() - 1, 0);
this.append(s);
I think you're supposed to be able to create new spannable text using the SpannableFactory, like so:
Spannable s = Spannable.Factory.getInstance().newSpannable(newText);
but I couldn't get this text to actually show new span effects, so I gave up.

Categories

Resources