Can I change TextView link text after using Linkify? - android

Is is possible to change TextView text after using Linkify to create links? I have something where I want the url to have two fields, a name and id, but then I just want the text to display the name.
So I start off with a textview with text that includes both name and id, and linkify to create the appropriate links with both fields. But for the display, I don't want to show the id.
Is this possible?

It's kind of a pain but yes. So Linkify basically does a few things. First it scans the contents of the textview for strings that match that of a url. Next it creates UrlSpan's and ForegroundColorSpan's for those sections that match it. Then it sets the MovementMethod of the TextView.
The important part here are the UrlSpan's. If you take your TextView and call getText(), notice it returns a CharSequence. It's most likely some sort of Spanned. From the Spanned you can ask, getSpans() and specifcally the UrlSpans. Once you know all those spans you can than loop through the list and find and replace the old span objects with your new span objects.
mTextView.setText(someString, TextView.BufferType.SPANNABLE);
if(Linkify.addLinks(mTextView, Linkify.ALL)) {
//You can use a SpannableStringBuilder here if you are going to
// manipulate the displayable text too. However if not this should be fine.
Spannable spannable = (Spannable) mTextView.getText();
// Now we go through all the urls that were setup and recreate them with
// with the custom data on the url.
URLSpan[] spans = spannable.getSpans(0, spannable.length, URLSpan.class);
for (URLSpan span : spans) {
// If you do manipulate the displayable text, like by removing the id
// from it or what not, be sure to keep track of the start and ends
// because they will obviously change.
// In which case you may have to update the ForegroundColorSpan's as well
// depending on the flags used
int start = spannable.getSpanStart(span);
int end = spannable.getSpanEnd(span);
int flags = spannable.getSpanFlags(span);
spannable.removeSpan(span);
// Create your new real url with the parameter you want on it.
URLSpan myUrlSpan = new URLSpan(Uri.parse(span.getUrl).addQueryParam("foo", "bar");
spannable.setSpan(myUrlSpan, start, end, flags);
}
mTextView.setText(spannable);
}
Hopefully that makes sense. Linkify is just a nice tool to setup the correct Spans. Spans just get interpreted when rendering text.

Greg's answer doesn't really answer the original question. But it does contain some insight as to where to start. Here's a function that you can use. It assumes that you have Linkified your textview prior to this call. It's in Kotlin, but you can get the gist of it if you are using Java.
In short, it builds a new Spannable with your new text. During the build, it copies over the url/flags of the URLSpans that the Linkify call created previously.
fun TextView.replaceLinkedText(pattern: String) { // whatever pattern you used to Linkify textview
if(this.text !is Spannable) return // no need to process since there are no URLSpans
val pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)
val matcher = pattern.matcher(this.text)
val linkifiedText = SpannableStringBuilder()
var cursorPos = 0
val spannable = this.text as Spannable
while (matcher.find()) {
linkifiedText.append(this.text.subSequence(cursorPos, matcher.start()))
cursorPos = matcher.end()
val span = spannable.getSpans(matcher.start(), matcher.end(), URLSpan::class.java).first()
val spanFlags = spannable.getSpanFlags(span)
val tag = matcher.group(2) // whatever you want to display
linkifiedText.append(tag)
linkifiedText.setSpan(URLSpan(span.url), linkifiedText.length - tag.length, linkifiedText.length, spanFlags)
}
this.text = linkifiedText
}

Related

How to make all 3 types of links in textview

I woduld like to make all of links in textview clickable.
The example text is:
"All three should link out http://google.com and here link and http://google.com"
If I use MovementMethod with the html text, only second and third link is clickable.
If I use Linkify(or mix both) only first and second link is clickable.
How can I make all of them clickable?
After invesigation I found that Linkify.addLinks() method remove current spans from text and apply new once (based on eg web page url). Because of that my spans from Html.fromHtml() was deleted at the beginning and never applay again.
So I did following:
1. Read thext from htmml Html.fromHtml which gives me Spanned obj with html spans.
2. Save spans from html in array
3. Make linkify.addLinks - this method remove my old spans so I will have to add it back
4. Add old spans
5. Set text to the textview.
Implementation:
private void setLabel(){
label.setText(linkifyHTML(Html.fromHtml("text with links here"));
label.setMovementMethod(LinkMovementMethod.getInstance());
label.setLinkTextColor(getRes().getColor(R.color.link));
}
private Spannable linkifyHTML(CharSequence text) {
Spannable s = new SpannableString(text);
URLSpan[] old = s.getSpans(0, s.length(), URLSpan.class);
LinkSpec oldLinks[] = new LinkSpec[old.length];
for (int i = 0; i < old.length; i++) {
oldLinks[i] = new LinkSpec(old[i], s.getSpanStart(old[i]), s.getSpanEnd(old[i]));
}
Linkify.addLinks(s, Linkify.ALL);
for (LinkSpec span : oldLinks) {
s.setSpan(span.span, span.start, span.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return s;
}
class LinkSpec {
final URLSpan span;
final int start, end;
public LinkSpec(URLSpan urlSpan, int spanStart, int spanEnd) {
span = urlSpan;
start = spanStart;
end = spanEnd;
}
}
You have to use the backslash \ to scape " character so the string will not consider it as the final point of the string. I mean, a string is considered when all the text is inside two "". You have to scape " characters in your url because if not the string will consider that it has to end when he find a new " character, in this case in your url.
"All three should link out http://google.com and here link and http://google.com"

Issue with spannable and Ellipsize in TextView

I have a news feed in my app. In a feed item I have a text view which may have url links and hashtags. I need the both the urls and the hashtags to be clickable which is implemented.
protected void setTextClickable(TextView txtView) {
L.i(getClass().getSimpleName(), "SET TEXT CLICKABLE ENTERED" + "LINES: " +String.valueOf(mTextViewLines));
String text = txtView.getText().toString();
ExpandableTextViewClickableSpan clickSpan = null;
ExpandableTextViewClickableSpan clickableSpan = null;
final SpannableString hashTagInText = new SpannableString(text);
String regexURL = "\\(?\\b((http|https)://|www[.])[-A-Za-z0-9+&##/%?=~_()|!:,.;]*[-A-Za-z0-9+&##/%=~_()|]";
String regexHashTag = "#([A-Za-z0-9_-]+)";
Matcher matcherURL = Pattern.compile(regexURL).matcher(hashTagInText);
Matcher matcherHashTag = Pattern.compile(regexHashTag).matcher(hashTagInText);
int color = view.getResources().getColor(R.color.tinted_green_colour);
while (matcherURL.find()) {
clickSpan = new ExpandableTextViewClickableSpan(currentFragment, false, color, hashTagInText,
matcherURL.start(), matcherURL.end(), MenuUtils.sURL_LINKID);
hashTagInText.setSpan(clickSpan, matcherURL.start(), matcherURL.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
while (matcherHashTag.find()) {
clickableSpan = new ExpandableTextViewClickableSpan(currentFragment, false, color, hashTagInText,
matcherHashTag.start(), matcherHashTag.end(), MenuUtils.sHASHTAG_LINKID);
hashTagInText.setSpan(clickableSpan, matcherHashTag.start(), matcherHashTag.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
However, there is also the requirement that if the message is longer than 4 lines then I ellipsis and show a view more/less option.
Since introducing the method to colour and set clickable the urls and hashtags (which works with spannables which then renders the ellipsis ineffective).
So now my ellipsis doesn't work. I came across this Spannable Ellipsis Issue on SO. However this requires a line count which I can't get unless it's done in a global layout listener.
Which I have done and successfully get the line count. The problem is however that the bind data method is called in which the spannable is applied and layout populated (where I want to apply my own ellipsis method) before the onGlobalLayout method is called, which then leaves me with 0 line count.
Is there a simple way to combine both ellipsis and spannable? Any help is appreciated.

Android - Breaking down a Spanned object

spanned = Html.fromHtml("<sup>aaa</sup>bbb<sub>ccc</sub><b>ddd</b>");
Will create a Spanned object with with 3 spans aaa, ccc, ddd.
bbb is being ignored since it's not inside an html tag,
spans = spanned.getSpans(0, spanned.length(), Object.class);
will only identify 3 spans.
I need a way to extract all the 4 sections of the code, if possible into some sort of an array that will allow to me to identify the the type of each span.
I need a way to extract all the 4 sections of the code
Use nextSpanTransition() to find the starting point of the next span. The characters between your initial position (first parameter to nextSpanTransition()) and the next span represent an unspanned portion of text.
You can take a look at the source code to the toHtml() method on the Html class to see this in action.
'bbb' is the one which is not inside html tag. Even though i guess it will not be missed. 'ccc' is a subscript, may be it is rendered but not visible to you. Try to increase textview height if you have constrained it.
use this http://developer.android.com/reference/android/text/Html.html#fromHtml(java.lang.String, android.text.Html.ImageGetter, android.text.Html.TagHandler), pass null for ImageGetter and your custom TagHandler
see the example
String source = "<b>bold</b> <i>italic</i> <unk>unknown</unk>";
TagHandler tagHandler = new TagHandler() {
Stack<Integer> starts = new Stack<Integer>();
#Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if (tag.equals("unk")) {
if (opening) {
starts.add(output.length());
} else {
int start = starts.pop();
int end = output.length();
Object what = new Object();
output.setSpan(what, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
};
Spanned spanned = Html.fromHtml(source, null, tagHandler);
TextUtils.dumpSpans(spanned, new LogPrinter(Log.DEBUG, TAG), "span ");

Any way to set span directly to spanable text?

This may be a blunder,But I need to know... :)
Iam develeoping an android application,In in I want to display two type face in a single textview and found this One very useful,A custom typeface span that extends typeface span,
As per my understanding , we are creating a spannable as follows using two words
String firstWord = "first ";
String secondWord = "second";
// Create a new spannable with the two strings
Spannable spannable = new SpannableString(firstWord+secondWord);
then sets two type face to that words using our custom span like this
// Set the custom typeface to span over a section of the spannable object
spannable.setSpan( new CustomTypefaceSpan("sans-serif",CUSTOM_TYPEFACE), 0,
firstWord.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan( new CustomTypefaceSpan("sans-serif",SECOND_CUSTOM_TYPEFACE), rstWord.length(), secondWord.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Now my question, I have a large amount of text so If i am able to put the spans directly in my text its will be a more easy for us ,is that possible to put spans directly to the text like we put <b>bold </b> as my need is to use my custom typeface span like <typeface1> sample </typeface1> <typeface2> Doc </typeface2>.
WHAT I NEED IS, DIRECTLY APPLY THE CHANGE TO TEXT WHAT THE SET SPAN DO TO THE TEXT
Is that possible, if possible what i need to write as span in my text,How can I achieve this.. or am i compleatly wrong ?
Thank you for all
Very late answer but what you asked for is pretty easy.
For instance, lets say we want to change everything between quotation marks into italic font.
String s = "\"Hello my name is Edward\" Hola, my nombre es Edward";
Pattern p = Pattern.compile("\"([^\"]*)\"");
Matcher m = p.matcher(text);
Spannable spannable = new SpannableString(s);
while (m.find()) {
int textLocation = text.indexOf(m.group(1));
// Set the custom typeface to span over a section of the spannable object
spannable.setSpan( new CustomTypefaceSpan("sans-serif",my_italic_font),
textLocation-1, textLocation + m.group(1).length(),spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Set the text of a textView with the spannable object
TextView textbox = (TextView) v.findViewById(R.id.cardtextbox);
}
textbox.setText( spannable );
Result of s would be
"Hello my name is Edward" Hola, my nombre es Edward
Now instead of quotation marks you would use a regex pattern for tags like <b>bold </b>;
And you could also remove the tags afterwards with a simple .replace();

Highlight multiple words in a string using SpannableString

I'm using a SpannableString to underline certain words, however, I realized the code I have only highlights the first word if there are multiple words. Not exactly sure how to accomplish highlighting multiple words:
String keyword = "test";
String text = "This is a test to underline the three test words in this test";
SpannableString output = new SpannableString(text);
if (text.indexOf(keyword) > -1)
{
int keywordIndex = text.indexOf(keyword);
int keywordLength = keyword.length();
int start = keywordIndex;
int end = keywordIndex + (keywordLength);
output.setSpan(new UnderlineSpan(), start, end, 0);
}
I was thinking I could split the text at every space and loop through it, but wasn't sure if there was a better way.
I do have this code to highlight multiple words using a regular expression, however, I'm try to avoid regular expressions since it's in an Android app and I'm using it in a ListView and I'm told they are very expensive. Also this code I have only highlight whole words, so using the example text above, if the word "protest" was in the sentence, it wouldn't get highlighted using this code:
Matcher matcher = Pattern.compile("\\b(?:test")\\b").matcher(text);
while (matcher.find())
{
output.setSpan(new UnderlineSpan(), matcher.start(), matcher.end(), 0);
}

Categories

Resources