How to Bold (Span) the searched text in string dynamically? - android

I have a string, in which i have to find a word being searched from end user and make it bold to highlight.
Example:
String : Address must have a format. Address can be of multiple line.
Required Text: Address must have a format. Address can be of multiple line.

After going through different Approaches. I have written the following method which will span the searched Text in string.
If you want to span the same text multiple time they can use this method as well.
public CharSequence highlightTextString(String completeText, String searchText) {
String temp = completeText.toLowerCase();
SpannableStringBuilder highlightText = new SpannableStringBuilder(completeText);
Pattern pattern = Pattern.compile(searchText.toLowerCase());
Matcher matcher = pattern.matcher(temp);
while (matcher.find()) {
StyleSpan styleSpan = new StyleSpan(android.graphics.Typeface.BOLD);
highlightText.setSpan(styleSpan, matcher.start(), matcher.end(), 0);
}
return highlightText;
}

Related

Detect words that have # or # and passing onClick event to it

Basically I have a string which looks like an instagram caption:
#username text text emoji#username2 #hastag text text #hastag2
The main issue here is that I could have a emoji in front of # or # characters, so I need to parse the text and find the words even if they are surrounded by emoji. After a run I should have click event for #username,#username2,#hashtag and #hashtag2.
So far I did:
String caption = "#username text text emoji#username2 #hastag text text #hastag2";
SpannableString ss = new SpannableString(caption);
String[] words = caption.split(" ");
for (final String word : words) {
if (word.startsWith("#") || word.startsWith("#")) {
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View textView) {
//Clicked word
}
};
ss.setSpan(clickableSpan, caption.indexOf(word), caption.indexOf(word) + word.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
txtComment.setText(ss);
txtComment.setMovementMethod(LinkMovementMethod.getInstance());
This does work for situations where there are no emoji envolved. How can I make this work on all scenarios ?
You need to create a regular expression Pattern, and then get a Matcher instance from it. Then you can iterate over the matches:
Pattern p = Pattern.compile("[##][a-zA-Z0-9]+"); //match letters or numbers after a # or #
Matcher m = p.matcher(caption); //get matcher, applying the pattern to caption string
while (m.find()) { // Find each match in turn
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View textView) {
//Clicked word
}
};
ss.setSpan(clickableSpan, m.start(), m.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
If you want to include underscores, you can use the RegEx suggested by #anubhava (which is equivalent to [##][a-zA-Z0-9_]+).
You can use this regex to get your matches starting with # or #:
[##]\w+\b
RegEx Demo
In Java:
Pattern p = Pattern.compile( "[##]\\w+\\b" );
PS: You will need to use Pattern and Matcher class in Java to get all the matches.

Highlight a particular word in a String dynamically

I want to highlight a particular word in a text view ( more specifically similar to a twitter feed). The word may occur multiple times. Below I will post a sample sentence from twitter.
" Mumbai Master Blaster! #Sachin. Greatest players of all times. The legend of cricket #sachin. "
Here I want to highlight the word " #Sachin " with a particular color. Also please note that we don't know how many times this word could get repeated in the whole string. Could anyone help me to solve this issue.
Use next code:
public CharSequence linkifyHashtags(String text) {
SpannableStringBuilder linkifiedText = new SpannableStringBuilder(text);
Pattern pattern = Pattern.compile("#\\w");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
String hashtag = text.substring(start, end);
ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);
linkifiedText.setSpan(span, 0, hashtag.length(), 0);
}
return linkifiedText;
}

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);
}

SpannableStringBuilder to create String with multiple fonts/text sizes etc Example?

I need to create a String placed in a TextView that will display a string like this:
First Part Not Bold BOLD rest not bold
So I want to know how I could use SpannableStringBuilder to do this?
I could use three TextEdit to accomplish this but I would like to use best solution.
First Part Not Bold BOLD rest not bold
You can do this either as #Rajesh suggested or by this.
String normalBefore= "First Part Not Bold ";
String normalBOLD= "BOLD ";
String normalAfter= "rest not bold";
String finalString= normalBefore+normalBOLD+normalAfter;
Spannable sb = new SpannableString( finalString );
sb.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), finalString.indexOf(normalBOLD)+ normalBOLD.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //bold
sb.setSpan(new AbsoluteSizeSpan(intSize), finalString.indexOf(normalBOLD)+ normalBOLD.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);//resize size
to show this in TextView
textview.setText(sb, TextView.BufferType.SPANNABLE);
The accepted answer is fine (and I upvoted it), but it fails to use the SpannableStringBuilder as the submitter requested. As I had a case where the Builder made the most sense, here is the code for that (with a bonus use of also changing the color of the text if that is helpful to others). Note that you could also provide the initial string to the SpannableStringBuilder constructor, but I set it here to use "append" to be clear that you can append a lot before your desired "bold" text and then just record the start as shown. I would suspect that this is also faster code than the accepted answer.
SpannableStringBuilder longDescription = new SpannableStringBuilder();
longDescription.append("First Part Not Bold ");
int start = longDescription.length();
longDescription.append("BOLD");
longDescription.setSpan(new ForegroundColorSpan(0xFFCC5500), start, longDescription.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
longDescription.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, longDescription.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
longDescription.append(" rest not bold");
If you are using Kotlin you can do the following using the android-ktx library
val s = SpannableStringBuilder()
.append("First Part Not Bold ")
.bold { append("BOLD") }
.append("Rest not bold")
The bold is an extension function on SpannableStringBuilder. You can see the documentation here for a list of operations you can use.
Another example:
val s = SpannableStringBuilder()
.color(green, { append("Green text ") })
.append("Normal text ")
.scale(0.5, { append("Text at half size " })
.backgroundColor(green, { append("Background green") })
Where green is a resolved RGB color.
It is even possible to nest spans so you end up with an embedded DSL:
bold { underline { italic { append("Bold and underlined") } } }
You will need the following in your app module level build.gradle for it to work:
repositories {
google()
}
dependencies {
implementation "androidx.core:core-ktx:1.2.0"
}
From API 21 SpannableStringBuilder includes a simple method to do this. Here is a solution example:
SpannableStringBuilder builder= new SpannableStringBuilder();
StyleSpan boldSpan = new StyleSpan(android.graphics.Typeface.BOLD);
builder.append("First Part Not Bold ")
.append("BOLD ", boldSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
.append("rest not bold");
Kotlin version:
val builder = SpannableStringBuilder()
val boldSpan = StyleSpan(Typeface.BOLD)
builder.append("First Part Not Bold ")
.append("BOLD ", boldSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
.append("rest not bold")
Use HTML code in TextView using the Html class:
Spanned styledText = Html.fromHtml("First Part Not Bold <b>BOLD</b> rest not bold");
textView.setText(styledText);
This code should set to bold everything that comes inside the html bold tag. And it also deletes the tag so only the content inside is displayed.
SpannableStringBuilder sb = new SpannableStringBuilder("this is <b>bold</b> and this is <b>bold too</b> and this is <b>bold too, again</b>.");
Pattern p = Pattern.compile("<b>.*?</b>", Pattern.CASE_INSENSITIVE);
boolean stop = false;
while (!stop)
{
Matcher m = p.matcher(sb.toString());
if (m.find()) {
sb.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), m.start(), m.end(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
sb.delete(m.end()-4, m.end());
sb.delete(m.start(), m.start() + 3);
}
else
stop = true;
}
This code can also be adapted for other html style tags, such as Superscript (sup tag), etc.
SpannableStringBuilder sb = new SpannableStringBuilder("text has <sup>superscript</sup> tag");
Pattern p = Pattern.compile("<sup>.*?</sup>", Pattern.CASE_INSENSITIVE);
boolean stop = false;
while (!stop)
{
Matcher m = p.matcher(sb.toString());
if (m.find()) {
sb.setSpan(new SuperscriptSpan(), m.start(), m.end(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
sb.delete(m.end()-6, m.end());
sb.delete(m.start(), m.start() + 5);
}
else
stop = true;
}
To set the color, just use the ForegroundColorSpan with setSpan.
sb.setSpan(new ForegroundColorSpan(Color.rgb(255, 0, 0)), m.start(), m.end(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
Hope it helps.
We can also use SpannableStringBuilder with TextAppearanceSpan to accomplish that. Follow the below steps to implement like that.
Create a style in styles.xml.
<style name="BoldStyle">
<!-- Can add other styling attributes -->
<item name="android:textStyle">bold</item>
......
</style>
Use the below code.
SpannableStringBuilder builder = new SpannableStringBuilder("First Part Not Bold BOLD rest not bold");
builder.setSpan(new TextAppearanceSpan(this, R.style.BoldStyle), 20, 24, 0);
((TextView)findViewById(R.id.tv7)).setText(builder);
That's it. Hope it'll help someone.
you can bold and resize a part of your string in kotlin
val s = SpannableStringBuilder()
.append("First Part Not Bold And No Resize ")
.bold { scale(1.5f, { append("Second Part By Bold And Resize " )}) }
.append("Third Part Not Bold And No Resize")
yourTextview.text = s
So I know this has been solved, and even as requested with SpannableStringBuilder but in the event you wanted to build a string more dynamically I figured I would put this up.
// Stuff needed
TextView DataTextView = (TextView)rootView.findViewById(R.id.DataView);
String Fields[] = {...database column names as strings... "x","y"};
String DataString = new String();
int start,stop; // Start and Stop of formatting
// Final Result
SpannableStringBuilder coloredString = new SpannableStringBuilder();
SpannableString temp; // Small segment of colored string
for (int i =0; i < Fields.length; i++)
{
if (database_result.containsKey(Fields[i])) // Be sure a field exists in the ContentValues
{
DataString = Fields[i]+": ";
start = DataString.length();
DataString = DataString+ +database_result.getAsInteger(Fields[i])+" ";
stop= DataString.length();
temp = new SpannableString(DataString);
temp.setSpan(new ForegroundColorSpan(Color.WHITE),start, stop, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
coloredString.append(temp);
}
}
DataTextView.setText(coloredString);
database_result is a ContentValues type that I constructed from the returned Cursor type of the SQL query. The only problem I had with this was at first it was only ColorSpaning the first segment. It seams that you need to declare a new ForegroundColorSpan every time you want to use one (or any other kind of span) in a loop.
Why would you use SpannableStringBuilder when you can use SpannableBuilder?? (https://gist.github.com/qtyq/90f9b4894069a8b3676c)
SpannableString ss = SpannableBuilder.init("First Part Not Bold BOLD rest not bold")
.makeBold("BOLD")
.create()
For Xamarin.Android:
SpannableStringBuilder TextoFormateado = new SpannableStringBuilder();
TextoFormateado.Append("Not Bold");
int start = TextoFormateado.Length();
TextoFormateado.Append("Bold and Red");
TextoFormateado.SetSpan(new ForegroundColorSpan(new Color(255, 0, 0, 149)),
start, TextoFormateado.Length(), SpanTypes.ExclusiveExclusive);
TextoFormateado.SetSpan(new StyleSpan(TypefaceStyle.Bold),
start, TextoFormateado.Length(), SpanTypes.ExclusiveExclusive);
TextoFormateado.Append("Not bold");
TxtFinalText.TextFormatted = TextoFormateado;

Can I change TextView link text after using Linkify?

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
}

Categories

Resources