I have a textview that should be in all caps and mark any urls found in it with a specific color, so naturaly I've tried textColorLink, with the option textAllCaps="true", however the url is not colored, my guess is that the regex does not match uppercase urls, since the url is colored if the same text is in lowercase.
I've tried solving it with this:
Spannable formatted = new SpannableString(text);
Pattern url = Pattern.compile(
"(https?)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]");
Matcher matcher = url.matcher(text.toLowerCase());
while (matcher.find())
{
Log.e("TEST",matcher.group());
int begIndex = matcher.start();
int endIdx = begIndex + matcher.group().length() - 1;
Log.e("Found", String.valueOf(begIndex));
formatted.setSpan(new ForegroundColorSpan(
getResources().getColor(android.R.color.holo_red_light)),
begIndex, endIdx, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
mTextView.setText(formatted);
Apparently it finds the text, however once again it is not colored. I've been at this for hours, how do you solve this?
when you try to upperCase the string lose the color but if you add another SpannableString and pass to this the string.toUpperCase than you can setSpan...
SpannableString formatted = new SpannableString(urlString);
Pattern url = Pattern.compile("(https?)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]");
Matcher matcher = url.matcher(urlString.toLowerCase());
//Here you save the string in upper case
SpannableString stringUpperCase = new SpannableString(formatted.toString().toUpperCase());
while (matcher.find()) {
int begIndex = matcher.start();
int endIdx = begIndex + matcher.group().length() - 1;
stringUpperCase.setSpan(new ForegroundColorSpan(R.color.Red),
0, formatted.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
TextView text = (TextView) findViewById(R.id.textView);
text.setText(string);
Should works...
Remove from xml the textAllCaps="true"
Related
I have a TextView and contains the below text
The -[[community]]- is here to help you with -[[specific]]- coding, -[[algorithm]]-, or -[[language]]- problems.
I want anything inside -[[]]- take red color, How can I do that using Spannable?
And I don't want to show -[[ and ]]- in TextView
You can use SpannableStringBuilder and append parts of String colorizing them when necessary. For example,
static CharSequence colorize(
String input, String open, String close, #ColorInt int color
) {
SpannableStringBuilder builder = new SpannableStringBuilder();
int openLen = open.length(), closeLen = close.length();
int openAt, contentAt, closeAt, last = 0;
while ((openAt = input.indexOf(open, last)) >= 0 &&
(closeAt = input
.indexOf(close, contentAt = openAt + openLen)) >= 0) {
int start = builder.append(input, last, openAt).length();
int len = builder.append(input, contentAt, closeAt).length();
builder.setSpan(
new ForegroundColorSpan(color),
start, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);
last = closeAt + closeLen;
}
return builder.append(input, last, input.length());
}
You can use the CodeView library to highlight many patterns with different colors, in your case for example the code will be like this
CodeView codeView = findViewById(R.id.codeview);
codeView.addSyntaxPattern(Pattern.compile("-\\[\\[[a-zA-Z]+]]-"), Color.GREEN);
codeView.setTextHighlighted(text);
And the result will be:
If the highlighted keywords are unique you can highlight them without using -[[]]- just create a pattern that can cover them
You can change the color, add or remove patterns in the runtime
CodeView Repository URL: https://github.com/amrdeveloper/codeview
The value in the variable who named open must be different from the value in the variable who named close, If the value was the same will cause a problem. You need to change variables values only to work well.
String open = "-[[";
String close = "]]-";
int color = Color.RED;
String s1 = "The -[[community]]- is here to help you with -[[specific]]- coding, -[[algorithm]]-, or -[[language]]- problems.";
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(s1);
while (spannableStringBuilder.toString().contains(open) && spannableStringBuilder.toString().contains(close)) {
spannableStringBuilder.setSpan(new ForegroundColorSpan(color), spannableStringBuilder.toString().indexOf(open) + open.length(), spannableStringBuilder.toString().indexOf(close) + close.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableStringBuilder.replace(spannableStringBuilder.toString().indexOf(open), spannableStringBuilder.toString().indexOf(open) + open.length(), "").replace(spannableStringBuilder.toString().indexOf(close), spannableStringBuilder.toString().indexOf(close) + close.length(), "");
}
yourTextView.setText(spannableStringBuilder);
I'm currently trying to figure out how to make text bold, Italic or underline with dynamic string coming from API, the text which has to be bold is coming as * bold *, Italic coming as _ italic_ and underline as #underline# (Same functionality as Stackoverflow).
After successful conversion of text, I want the special chars to be removed as well.
Text from API -
* I am Bold* and love to see _myself and _ others too.
Expected answer - I am Bold and love to see myself and others too.
I have tried some code which does not work if I try to create italic after bold also if I try to remove special chars.
TextView t = findViewById(R.id.viewOne);
String text = "*I am Bold* and _I am Italic_ here *Bold too*";
SpannableStringBuilder b = new SpannableStringBuilder(text);
Matcher matcher = Pattern.compile(Pattern.quote("*") + "(.*?)" + Pattern.quote("*")).matcher(text);
while (matcher.find()){
String name = matcher.group(1);
int index = text.indexOf(name)-1;
b.setSpan(new StyleSpan(Typeface.BOLD), index, index + name.length()+1, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
}
t.setText(b);
I don't want to use HTML tags
Edited answer to address the edited question
Try below, you should had to pass typeface instead StyleSpan.
public class SpanTest extends Activity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
TextView test = findViewById(R.id.test);
// String text = "*I am Bold* and _I am Italic_ here *Bold too*";
String text = "* I am Bold* and love to see _myself and _ others too";
CharSequence charSequence = updateSpan(text, "*", Typeface.BOLD);
charSequence = updateSpan(charSequence, "_", Typeface.ITALIC);
test.setText(charSequence);
}
private CharSequence updateSpan(CharSequence text, String delim, int typePace) {
Pattern pattern = Pattern.compile(Pattern.quote(delim) + "(.*?)" + Pattern.quote(delim));
SpannableStringBuilder builder = new SpannableStringBuilder(text);
if (pattern != null) {
Matcher matcher = pattern.matcher(text);
int matchesSoFar = 0;
while (matcher.find()) {
int start = matcher.start() - (matchesSoFar * 2);
int end = matcher.end() - (matchesSoFar * 2);
StyleSpan span = new StyleSpan(typePace);
builder.setSpan(span, start + 1, end - 1, 0);
builder.delete(start, start + 1);
builder.delete(end - 2, end - 1);
matchesSoFar++;
}
}
return builder;
}
}
Here is the output.
I am looking to change the text of a TextView view via the .setText("") method while also coloring a part of the text (or making it bold, italic, transparent, etc.)and not the rest. For example:
title.setText("Your big island <b>ADVENTURE!</b>";
I know the above code is incorrect but it helps illustrate what I would like to achieve. How would I do this?
Use spans.
Example:
final SpannableStringBuilder sb = new SpannableStringBuilder("your text here");
// Span to set text color to some RGB value
final ForegroundColorSpan fcs = new ForegroundColorSpan(Color.rgb(158, 158, 158));
// Span to make text bold
final StyleSpan bss = new StyleSpan(android.graphics.Typeface.BOLD);
// Set the text color for first 4 characters
sb.setSpan(fcs, 0, 4, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
// make them also bold
sb.setSpan(bss, 0, 4, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
yourTextView.setText(sb);
title.setText(Html.fromHtml("Your big island <b>ADVENTURE!</b>"));
I hope this helps you (it works with multi language).
<string name="test_string" ><![CDATA[<font color="%1$s"><b>Test/b></font>]]> String</string>
And on your java code, you can do:
int color = context.getResources().getColor(android.R.color.holo_blue_light);
String string = context.getString(R.string.test_string, color);
textView.setText(Html.fromHtml(string));
This way, only the "Test" part will be colored (and bold).
If you are using Kotlin you can do the following using the android-ktx library
val title = SpannableStringBuilder()
.append("Your big island ")
.bold { append("ADVENTURE") }
titleTextField.text = title
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 ssb = SpannableStringBuilder()
.color(green) { append("Green text ") }
.append("Normal text ")
.scale(0.5F) { 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 something like 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:0.3'
}
Here's an example that will look for all occurrences of a word (case insensitive), and color them red:
String notes = "aaa AAA xAaax abc aaA xxx";
SpannableStringBuilder sb = new SpannableStringBuilder(notes);
Pattern p = Pattern.compile("aaa", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(notes);
while (m.find()){
//String word = m.group();
//String word1 = notes.substring(m.start(), m.end());
sb.setSpan(new ForegroundColorSpan(Color.rgb(255, 0, 0)), m.start(), m.end(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
editText.setText(sb);
You can use a Spannable to give certain parts of a text certain aspects. I can look up an example if you want.
Ah, from right here on stackoverflow.
TextView TV = (TextView)findViewById(R.id.mytextview01);
Spannable WordtoSpan = new SpannableString("I know just how to whisper, And I know just how to cry,I know just where to find the answers");
WordtoSpan.setSpan(new ForegroundColorSpan(Color.BLUE), 15, 30, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
TV.setText(WordtoSpan);
If you want to use HTML, you need to use TextView.setText(Html.fromHtml(String htmlString))
If you want to do that often / repeatedly, you may have a look at a class (SpannableBuilder) I wrote, as Html.fromHtml() is not very efficient (it is using a big xml parsing machinery inside). It is described in this blog posting.
String str1 = "If I forget my promise to ";
String penalty = "Eat breakfast every morning,";
String str2 = " then I ";
String promise = "lose my favorite toy";
String strb = "<u><b><font color='#081137'>"+ penalty +",</font></b></u>";
String strc = "<u><b><font color='#081137'>"+ promise + "</font></b></u>";
String strd = str1 +strb+ str2 + strc;
tv_notification.setText(Html.fromHtml(strd));
or use this code:
SpannableStringBuilder builder = new SpannableStringBuilder();
SpannableString text1 = new SpannableString(str1);
text1.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.silver)), 0, str1.length() - 1, 0);
builder.append(text1);
SpannableString text2 = new SpannableString(penalty);
text2.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.midnight)), 0, penalty.length(), 0);
text2.setSpan(new UnderlineSpan(), 0, penalty.length(), 0);
builder.append(text2);
SpannableString text3 = new SpannableString(str2);
text3.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.silver)),0, str2.length(), 0);
builder.append(text3);
SpannableString text4 = new SpannableString(promise);
text4.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.midnight)), 0, promise.length(), 0);
text4.setSpan(new UnderlineSpan(),0, promise.length(), 0);
builder.append(text4);
tv_notification.setText(builder);
I like to use SpannableStringBuilder by appending the different spans one by one, rather than calling setSpan by calculating the string lengths
as: (Kotlin code)
val amountSpannableString = SpannableString("₹$amount").apply {
// text color
setSpan(ForegroundColorSpan("#FD0025".parseColor()), 0, length, 0)
// text size
setSpan(AbsoluteSizeSpan(AMOUNT_SIZE_IN_SP.spToPx(context)), 0, length, 0)
// font medium
setSpan(TypefaceSpan(context.getString(R.string.font_roboto_medium)), 0, length, 0)
}
val spannable: Spannable = SpannableStringBuilder().apply {
// append the different spans one by one
// rather than calling setSpan by calculating the string lengths
append(TEXT_BEFORE_AMOUNT)
append(amountSpannableString)
append(TEXT_AFTER_AMOUNT)
}
public static void setColorForPath(Spannable spannable, String[] paths, int color) {
for (int i = 0; i < paths.length; i++) {
int indexOfPath = spannable.toString().indexOf(paths[i]);
if (indexOfPath == -1) {
continue;
}
spannable.setSpan(new ForegroundColorSpan(color), indexOfPath,
indexOfPath + paths[i].length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
Using
Spannable spannable = new SpannableString("Your big island ADVENTURE");
Utils.setColorForPath(spannable, new String[] { "big", "ADVENTURE" }, Color.BLUE);
textView.setText(spannable);
You can concatenate two or more Spans. This way is easier to color dynamic text using length value.
SpannableStringBuilder span1 = new SpannableStringBuilder("Android");
ForegroundColorSpan color1=new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary));
span1.setSpan(color1, 0, span1.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
SpannableStringBuilder span2 = new SpannableStringBuilder("Love");
ForegroundColorSpan color2=new ForegroundColorSpan(getResources().getColor(R.color.colorSecondary));
span2.setSpan(color2, 0, span2.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
Spanned concatenated=(Spanned) TextUtils.concat(span1," => ",span2);
SpannableStringBuilder result = new SpannableStringBuilder(concatenated);
TextView tv = (TextView) rootView.findViewById(R.id.my_texview);
tv.setText(result, TextView.BufferType.SPANNABLE);
Use this code its helpful
TextView txtTest = (TextView) findViewById(R.id.txtTest);
txtTest.setText(Html.fromHtml("This is <font color="#ff4343">Red</font> Color!"));
You can use extension function in Kotlin
fun CharSequence.colorizeText(
textPartToColorize: CharSequence,
#ColorInt color: Int
): CharSequence = SpannableString(this).apply {
val startIndexOfText = this.indexOf(textPartToColorize.toString())
setSpan(ForegroundColorSpan(color), startIndexOfText, startIndexOfText.plus(textPartToColorize.length), 0)
}
Usage:
val colorizedText = "this text will be colorized"
val myTextToColorize = "some text, $colorizedText continue normal text".colorizeText(colorizedText,ContextCompat.getColor(context, R.color.someColor))
Html.fromHtml is deprecated
Use HtmlCompat instead
HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
If you do not want to get in trouble on lower SDK version use SpannableStringBuilder with ForegroundColorSpan or BackgroundColorSpan as HtmlCompat.fromHtml color style does not applied on older Android version.
I want to highlight certain text background with some color with case insensitive. I tried the code below but it's not working. It only highlights when the keyword is in lowercase.
private static CharSequence highlightText(String search, String originalText) {
if (search != null && !search.equalsIgnoreCase("")) {
String normalizedText = Normalizer.normalize(originalText, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "").toLowerCase().;
int start = normalizedText.indexOf(search);
if (start < 0) {
return originalText;
} else {
Spannable highlighted = new SpannableString(originalText);
while (start >= 0) {
int spanStart = Math.min(start, originalText.length());
int spanEnd = Math.min(start + search.length(), originalText.length());
highlighted.setSpan(new BackgroundColorSpan(Color.YELLOW), spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
start = normalizedText.indexOf(search, spanEnd);
}
return highlighted;
}
}
return originalText;
}
For example I have a original text = "I Love Stackoverflow" and the keyword is "i love". How can I highlight the text background of "i love" without changing it to lower case and maintain the case.
Thank you.
I got the answer from here:
Android: Coloring part of a string using TextView.setText()?
String notes = "aaa AAA xAaax abc aaA xxx";
SpannableStringBuilder sb = new SpannableStringBuilder(notes);
Pattern p = Pattern.compile("aaa", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(notes);
while (m.find()){
//String word = m.group();
//String word1 = notes.substring(m.start(), m.end());
sb.setSpan(new BackgroundColorSpan(Color.YELLOW), m.start(), m.end(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
editText.setText(sb);
As an update Mei Yi's answer:
If you set a layout attribute on the TextView such as android:textAllCaps="true" it may overwrite the Spannable string used to set the highlight and look like it isn't working. That is easy to work around; just set the layout attributes programmatically.
Ex. textView.setText(text.toUpperCase()) instead of android:textAllCaps="true"
This will solve your issue
String text = "I Love StackOverflow";
String hilyt = "i love";
//to avoid issues ahead make sure your
// to be highlighted exists in de text
if( !(text.toLowerCase().contains(hilyt.toLowerCase())) )
return;
int x = text.toLowerCase().indexOf(hilyt.toLowerCase());
int y = x + hilyt.length();
Spannable span = new SpannableString(text);
span.setSpan(new BackgroundColorSpan(Color.YELLOW), x, y, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
yourTextView.setText(span);
The secret is changing all cases of both strings to lower case while trying to get de position of our text to be highlighted.
I hope it will help someone.
What would be the correct way to find str1 as a substring of str2, make it bold (in str2, that is), and then display it as a textview?
Use str2 as SpannableString and use setSpan() method to find substring str1 and make it BOLD.
See documentation.
Try this:
TextView textView = (TextView) findViewById(R.id.textView);
String str1 = "substring";
String str2 = "Find a substring and make it bold";
if (str2.contains(str1)) {
SpannableString spannable = new SpannableString(str2);
spannable.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
str2.indexOf(str1),
str2.indexOf(str1) + str1.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(spannable);
} else {
textView.setText(str2);
}
OUTPUT:
Hope this will help~
you can use Spannable String for that
if(str2.contain(str1){
SpannableStringBuilder sb = new SpannableStringBuilder(str2);
// Span to make text bold
StyleSpan bss = new StyleSpan(android.graphics.Typeface.BOLD);
// make them also bold
sb.setSpan(bss, str2.indexOf(str1), str1.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(sb);
}else{
textView.setText(str2);
}
String str1 = "hello world, how are you?";
String str2 = "world";
//check if str1 contains str2
if (!str1.contains(str2)) {
return;
}
//find index of str2
int startIndex = str1.indexOf(str2);
int endIndex = startIndex + str2.length();
//create spannable
SpannableString spannable = new SpannableString(str2;
spannable.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), startIndex, endIndex, 0);
//set it to textview
textView.setText(spannable);
Use indexOf to find sub1 and Html.fromHtml to format string
int start = str2.indexOf(str1);
String destStr = str2.substring(start)+"<b>" + str2.substring(start, str1.length()) + "</b> " + str2.substring(start+str1.length());
textview.setText(Html.fromHtml(destStr));
With the help of the people who commented here, I created a function that also ignores case - to use, textView.setText(boldifySubstring(str2, str1)); -
public static Spanned boldifySubstring(String string, String substring) {
//Create spannable string
SpannableString spannable = new SpannableString(string);
//Check if substring exists (ignore case)
if (string.toLowerCase().contains(substring.toLowerCase())) {
//Get start + end indexes (ignore case)
int start = string.toLowerCase().indexOf(substring.toLowerCase());
int end = start + substring.length();
//Make relevant parts bold
spannable.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
return spannable;
}
Here is some Kotlin code which will find all occurrences of a string in a CharSequence and apply specified styles (Spans) to the found occurrences.
"Hello World? Hello World!".applySpansToSubString("world", StyleSpan(Typeface.BOLD))
will return "Hello World? Hello World!"
fun CharSequence.applySpansToSubString(searchString: String, vararg spans: CharacterStyle): CharSequence {
if(searchString.isEmpty()) { return this }
val sb = SpannableString(this)
var startIndex = 0
while(startIndex in 0 until length) {
startIndex = indexOf(searchString, startIndex, true)
if (startIndex >= 0) {
val endIndex = startIndex + searchString.length
for (span in spans) {
sb.setSpan(CharacterStyle.wrap(span), startIndex, endIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
startIndex = endIndex
}
}
return sb
}