Related
text = text + CepVizyon.getPhoneCode() + "\n\n"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText();
activationText.setText(text);
myTextView.setText(text);
I want to change color for CepVizyon.getPhoneCode()'s string. How can I do this?
Spannable is more flexible:
String text2 = text + CepVizyon.getPhoneCode() + "\n\n"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText();
Spannable spannable = new SpannableString(text2);
spannable.setSpan(new ForegroundColorSpan(Color.WHITE), text.length(), (text + CepVizyon.getPhoneCode()).length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
myTextView.setText(spannable, TextView.BufferType.SPANNABLE);
If you have static text that needs color, you can add it without any code via the strings file:
<string name="already_have_an_account">Already have an account? <font color='#01C6DB'>Login</font></string>
then
<TextView
android:layout_width="wrap_content"
android:layout_height="64dp"
android:text="#string/already_have_an_account"/>
result
I'm not sure which API versions this works on, but it doesn't work for API 19 that I've tested so far, so probably only some of the most recent API versions support this.
As #hairraisin mentioned in the comments, try using fgcolor instead of color for the font color, then it should work for lower API levels, but need more testing to be sure.
myTextView.setText(Html.fromHtml(text + "<font color=white>" + CepVizyon.getPhoneCode() + "</font><br><br>"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText()));
Here solution in Kotlin that uses SpannableString to change color of part of a string.
val phoneCodeColor = ContextCompat.getColor(this, R.color.myColor)
val text = SpannableStringBuilder()
.color(phoneCodeColor) { append("${ CepVizyon.getPhoneCode() }") }
.append("\n\n")
.append(getString(R.string.currentversion))
.append(${ CepVizyon.getLicenseText() })
activationText.text = text
myTextView.text = text
With regards to Maneesh's answer, this will work but you need to add and escape the quotes for the color attribute.
myTextView.setText(Html.fromHtml(text + "<font color=\"#FFFFFF\">" + CepVizyon.getPhoneCode() + "</font><br><br>"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText()));
It is good for me!
Spannable spannable = new SpannableString("ABC In-Network DEF");
String str = spannable.toString();
iStart = str.indexOf("In-Network");
iEnd = iStart + 10;/*10 characters = in-network. */
SpannableString ssText = new SpannableString(spannable);
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View widget) {
//your code at here.
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(true);
ds.setColor(getResources().getColor(R.color.green));
}
};
ssText.setSpan(clickableSpan, iStart, iEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.setText(ssText);
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
mTextView.setHighlightColor(Color.TRANSPARENT);
mTextView.setEnabled(true);
Here's a colorize function based on andyboot's answer:
/**
* Colorize a specific substring in a string for TextView. Use it like this: <pre>
* textView.setText(
* Strings.colorized("The some words are black some are the default.","black", Color.BLACK),
* TextView.BufferType.SPANNABLE
* );
* </pre>
* #param text Text that contains a substring to colorize
* #param word The substring to colorize
* #param argb The color
* #return the Spannable for TextView's consumption
*/
public static Spannable colorized(final String text, final String word, final int argb) {
final Spannable spannable = new SpannableString(text);
int substringStart=0;
int start;
while((start=text.indexOf(word,substringStart))>=0){
spannable.setSpan(
new ForegroundColorSpan(argb),start,start+word.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
substringStart = start+word.length();
}
return spannable;
}
I have made this little function, just pass in your text to color, the start and end indexes of what you want to color of that text and the color itself
Kotlin
private fun colorMyText(inputText:String,startIndex:Int,endIndex:Int,textColor:Int):Spannable{
val outPutColoredText: Spannable = SpannableString(inputText)
outPutColoredText.setSpan(
ForegroundColorSpan(textColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return outPutColoredText
}
Usage
txt_comment.text = colorMyText("Comentario: ${item.comentario}",0,13,Color.BLACK)
Use character escapes + Html.fromHtml()
How to store the String in the string resource folder
<string name="textFromRes">
<font color="#FF0000">This is colored in red </font> This is not
</string>
How to show in TextView?
String text = this.getResources().getString(R.string.textFromRes);
htmlText.setText(Html.fromHtml(text));
Bonus:
The String in the output looks like this
<string name="textFromRes">
<font color="#FF0000">This is colored in red </font> This is not
<br />
<h1> This is h1 heading </h1>
<br />
<h3> This is h2 subheading</h3>
<br />
<b> This text is bold</b>
<br />
<i> This text is italic</i>
<br />
Android users expect your app to look and behave in a way that is
consistent with the platform. Not only should you follow material
design guidelines for visual and navigation patterns,
but you should also follow quality guidelines for compatibility,
performance, security, and more.
<br />
<br />
The following links provide everything you need to design a high quality Android app.
</string>
With a general-purpose Kotlin extension function, it would look like this:
/**
* Change the color of a part of the text contained in this textView
*
* #param subStringToColorize has to already be set in the textView's text
* #param colorResId
*/
fun TextView.colorize(subStringToColorize: String, #ColorRes colorResId: Int) {
val spannable: Spannable = SpannableString(text)
val startIndex = text.indexOf(subStringToColorize, startIndex = 0, ignoreCase = false)
val endIndex = startIndex + subStringToColorize.length
val color: Int = ContextCompat.getColor(context, colorResId)
if (startIndex != -1) {
spannable.setSpan(ForegroundColorSpan(color),
startIndex,
endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
setText(spannable, TextView.BufferType.SPANNABLE)
}
}
I didn't like the idea of doing this by code every time i want to color parts of the text which i have been doing a lot in all of my apps (and since in some case text is being set in runtime with different inline-defined colors) so i created my own MarkableTextView.
The idea was to:
Detect XML tags from string
Identify and match tag name
Extract and save attributes and position of text
Remove tag and keep content
Iterate through attributes and apply styles
Here's the process step by step:
First i needed a way to find XML tags in a given string and Regex did the trick..
<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\s+([^>]*))?>([^>][^<]*)</\1\s*>
For the above to match an XML tag it has to have the following criteria:
Valid tag name like <a> <a > <a-a> <a ..attrs..> but not < a> <1>
Closing tag that has a matching name like <a></a> but not <a></b>
Any content, since there's no need to style "nothing"
Now for the attributes we're going to use this one..
([a-zA-Z]+)\s*=\s*(['"])\s*([^'"]+?)\s*\2
It has the same concept and generally i didn't need to go far for both since the compiler will take care of the rest if anything goes out of format.
Now we need a class that can hold the extracted data:
public class MarkableSheet {
private String attributes;
private String content;
private int outset;
private int ending;
private int offset;
private int contentLength;
public MarkableSheet(String attributes, String content, int outset, int ending, int offset, int contentLength) {
this.attributes = attributes;
this.content = content;
this.outset = outset;
this.ending = ending;
this.offset = offset;
this.contentLength = contentLength;
}
public String getAttributes() {
return attributes;
}
public String getContent() {
return content;
}
public int getOutset() {
return outset;
}
public int getContentLength() {
return contentLength;
}
public int getEnding() {
return ending;
}
public int getOffset() {
return offset;
}
}
Before anything else, we're going to add this cool iterator that i've been using for long to loop through matches (can't remember the author):
public static Iterable<MatchResult> matches(final Pattern p, final CharSequence input) {
return new Iterable<MatchResult>() {
public Iterator<MatchResult> iterator() {
return new Iterator<MatchResult>() {
// Use a matcher internally.
final Matcher matcher = p.matcher(input);
// Keep a match around that supports any interleaving of hasNext/next calls.
MatchResult pending;
public boolean hasNext() {
// Lazily fill pending, and avoid calling find() multiple times if the
// clients call hasNext() repeatedly before sampling via next().
if (pending == null && matcher.find()) {
pending = matcher.toMatchResult();
}
return pending != null;
}
public MatchResult next() {
// Fill pending if necessary (as when clients call next() without
// checking hasNext()), throw if not possible.
if (!hasNext()) { throw new NoSuchElementException(); }
// Consume pending so next call to hasNext() does a find().
MatchResult next = pending;
pending = null;
return next;
}
/** Required to satisfy the interface, but unsupported. */
public void remove() { throw new UnsupportedOperationException(); }
};
}
};
}
MarkableTextView:
public class MarkableTextView extends AppCompatTextView {
public MarkableTextView(Context context) {
super(context);
}
public MarkableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MarkableTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void setText(CharSequence text, BufferType type) {
// Intercept and process text
text = prepareText(text.toString());
super.setText(text, type);
}
public Spannable Markable;
private Spannable prepareText(String text) {
String parcel = text;
Multimap<String, MarkableSheet> markableSheets = ArrayListMultimap.create();
// Used to correct content position after tossing tags
int totalOffset = 0;
// Iterate through text
for (MatchResult match : matches(Markable.Patterns.XML, parcel)) {
// Get tag name
String tag = match.group(1);
// Match with a defined tag name "case-sensitive"
if (!tag.equals(Markable.Tags.MARKABLE)) {
// Break if no match
break;
}
// Extract data
String attributes = match.group(2);
String content = match.group(3);
int outset = match.start(0);
int ending = match.end(0);
int offset = totalOffset; // offset=0 since no preceded changes happened
int contentLength = match.group(3).length();
// Calculate offset for the next element
totalOffset = (ending - outset) - contentLength;
// Add to markable sheets
MarkableSheet sheet =
new MarkableSheet(attributes, content, outset, ending, offset, contentLength);
markableSheets.put(tag, sheet);
// Toss the tag and keep content
Matcher reMatcher = Markable.Patterns.XML.matcher(parcel);
parcel = reMatcher.replaceFirst(content);
}
// Initialize spannable with the modified text
Markable = new SpannableString(parcel);
// Iterate through markable sheets
for (MarkableSheet sheet : markableSheets.values()) {
// Iterate through attributes
for (MatchResult match : matches(Markable.Patterns.ATTRIBUTES, sheet.getAttributes())) {
String attribute = match.group(1);
String value = match.group(3);
// Apply styles
stylate(attribute,
value,
sheet.getOutset(),
sheet.getOffset(),
sheet.getContentLength());
}
}
return Markable;
}
Finally, styling, so here's a very simple styler i made for this answer:
public void stylate(String attribute, String value, int outset, int offset, int length) {
// Correct position
outset -= offset;
length += outset;
if (attribute.equals(Markable.Tags.TEXT_STYLE)) {
if (value.contains(Markable.Tags.BOLD) && value.contains(Markable.Tags.ITALIC)) {
Markable.setSpan(
new StyleSpan(Typeface.BOLD_ITALIC),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (value.contains(Markable.Tags.BOLD)) {
Markable.setSpan(
new StyleSpan(Typeface.BOLD),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (value.contains(Markable.Tags.ITALIC)) {
Markable.setSpan(
new StyleSpan(Typeface.ITALIC),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (value.contains(Markable.Tags.UNDERLINE)) {
Markable.setSpan(
new UnderlineSpan(),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
if (attribute.equals(Markable.Tags.TEXT_COLOR)) {
if (value.equals(Markable.Tags.ATTENTION)) {
Markable.setSpan(
new ForegroundColorSpan(ContextCompat.getColor(
getContext(),
R.color.colorAttention)),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (value.equals(Markable.Tags.INTERACTION)) {
Markable.setSpan(
new ForegroundColorSpan(ContextCompat.getColor(
getContext(),
R.color.colorInteraction)),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
And here's how the Markable class containing the definitions looks like:
public class Markable {
public static class Patterns {
public static final Pattern XML =
Pattern.compile("<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\\s+([^>]*))?>([^>][^<]*)</\\1\\s*>");
public static final Pattern ATTRIBUTES =
Pattern.compile("(\\S+)\\s*=\\s*(['\"])\\s*(.+?)\\s*\\2");
}
public static class Tags {
public static final String MARKABLE = "markable";
public static final String TEXT_STYLE = "textStyle";
public static final String BOLD = "bold";
public static final String ITALIC = "italic";
public static final String UNDERLINE = "underline";
public static final String TEXT_COLOR = "textColor";
public static final String ATTENTION = "attention";
public static final String INTERACTION = "interaction";
}
}
All that we need now is to reference a string and basically it should look like this:
<string name="markable_string">
<![CDATA[Hello <markable textStyle=\"underline\" textColor=\"interaction\">world</markable>!]]>
</string>
Make sure to wrap the tags with a CDATA Section and escape " with \.
I made this as a modular solution to process parts of the text in all different ways without the need of stuffing unnecessary code behind.
I did as andy boot said, but i had a clickable span as well, and it didn't work because the order the setSpans were called. So you have to first call the spannable.setSpan(clickableSpanand... then the spannable.setSpan(new ForegroundColorSpan... to get the color in the TextView
Inspired by Alejandro H. Cruz's answer above.
His function only works for a single substring match, I have updated his method to use Regex and should update colors on all matches:
fun TextView.colorizeAll(subStringToColorize: String, #ColorRes colorResId: Int) {
val color: Int = ContextCompat.getColor(context, colorResId)
val spannable: Spannable = SpannableString(text)
val pattern = subStringToColorize.toRegex()
val matches = pattern.findAll(text, 0)
matches.forEach { match ->
val startIndex = match.range.first
val endIndex = match.range.last + match.range.step
spannable.setSpan(ForegroundColorSpan(color),
startIndex,
endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
setText(spannable, TextView.BufferType.SPANNABLE)
}
}
I created this little helper method that can be called from a TextView:
fun TextView.attributedString(
forText: String,
foregroundColor: Int? = null,
style: StyleSpan? = null
) {
val spannable: Spannable = SpannableString(text)
// check if the text we're highlighting is empty to abort
if (forText.isEmpty()) {
return
}
// compute the start and end indices from the text
val startIdx = text.indexOf(forText)
val endIdx = startIdx + forText.length
// if the indices are out of bounds, abort as well
if (startIdx < 0 || endIdx > text.length) {
return
}
// check if we can apply the foreground color
foregroundColor?.let {
spannable.setSpan(
ForegroundColorSpan(it),
startIdx,
endIdx,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
}
// check if we have a stylespan
style?.let {
spannable.setSpan(
style,
startIdx,
endIdx,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
}
// apply it
text = spannable
}
To use it:
plateText.text = "Hello world!"
// This will color the part "world" to whatever color you have defined
// And make the word **bold**.
plateText.attributedString(
"world",
getColor(R.color.colorMatchingText, null),
StyleSpan(Typeface.BOLD)
)
Tested on API 29, cheers!
SpannableStringBuilder builder = new SpannableStringBuilder();
String the = "The ";
SpannableString theSpannable= new SpannableString(the);
builder.append(theSpannable);
String author = "author ";
SpannableString authorSpannable= new SpannableString(author);
authorSpannable.setSpan(new RelativeSizeSpan(1.2f), 0,authorSpannable.length(), 0); // set size
authorSpannable.setSpan(new ForegroundColorSpan(Color.BLACK), 0, authorSpannable.length(), 0);
builder.append(authorSpannable);
String has = "has ";
SpannableString hasSpannable= new SpannableString(has);
builder.append(hasSpannable);
String approved = "approved ";
SpannableString approvedSpannable= new SpannableString(approved);
approvedSpannable.setSpan(new RelativeSizeSpan(1.2f), 0,approvedSpannable.length(), 0); // set size
StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
approvedSpannable.setSpan(boldSpan, 0, approvedSpannable.length() + 0, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
approvedSpannable.setSpan(new ForegroundColorSpan(ContextCompat.getColor(this, R.color.CLR_PRESSED_SAVED)), 0,
approvedSpannable.length(), 0);
builder.append(approvedSpannable);
String white = "your access to this share. Do you want re-access now?";
SpannableString whiteSpannable= new SpannableString(white);
builder.append(whiteSpannable);
_AccessStatusText.setText(builder, TextView.BufferType.SPANNABLE);
its Simple
String text = "We've sent the code to ";
String text2 = text + getEmail() + "\n\n";
Spannable spannable = new SpannableString(text2);
spannable.setSpan(new ForegroundColorSpan(Color.BLUE), text.length(), (text + getEmail()).length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mBinding.textViewStatus.setText(spannable, TextView.BufferType.SPANNABLE);
DO NOT USE Html.fromHtml to avoid fragmented behavior. Spannable or SpannableStringBuilder is the best way to do this with ForegroundColorSpan or BackgroundColorSpan depending on your requirements. Html or HtmlCompat tag even with style color or background-color is not realiable as it does not work on all SDK specially on lower version such as 21, same case for emojis.
Example case:
<span style=\"background-color:red\">⬆</span>
When the Html string above converts to Spanned using HtmlCompat.fromHtml and use it in setText() the style does not work on older SDK version.
One way is to split myTextView to few separate TextViews, one of which would be only for phone code. Then controlling color of this specific TextView is pretty straight-forward.
I am trying to set BackgroundColorSpan to selected text in my Edit-text. So when i select any text and click on button it will set Background color to that particular text and then i am saving that note to my SDCard with .html format and then again i am retrieving that note to edit again in the same format.
The problem i am facing right now is when i apply BackgroundColorSpan to selected text it show's that string with background color which i applied but once i save that note to my SDCard and re-open, it does not show Background color to that particular string instead of that it show me normal string without background color.
Below is my code Which i used to set Background Color to Edit-text selected area
mSpannable.setSpan(new BackgroundColorSpan(color),startSelection, endSelection, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
and below is code to save my notes to SDcard.
Spanned spannedText = edtNoteDescription.getText();
StringBuilder output = new StringBuilder();
AppHtml.withinHtml(output, spannedText);
File mFile = new File(Environment.getExternalStorageDirectory()
+ File.separator + "MyNote/");
}
File myFile = new File(mFile, "/" + strNoteTitle + ".html/");
myFile.createNewFile();
FileOutputStream fOut = new FileOutputStream(myFile);
OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut);
myOutWriter.append(output);
myOutWriter.close();
fOut.close();
With this above code i am successfully able to save my file in HTML format, but i am not getting the string with Background color.
I tried to print that string in Log and then pasted that string in w3School then i get exact result what i expect it to be but in android i don't know why it is not working.
String which i get in Logcat is below
<p><font color ="#7dff00">This</font> <font color ="#ff5100">Is</font> a <font color ="#04ff00"><b><font style = "background-color:#2929dd">String</font></b></font>... </p>
You can try this string here which gives perfect result with background color to string but while setting to android Edit-ext i don't know what is happening and it's not setting as i expect it to be.
Edit
Below is code which i used to retrieve text from my SDcard file
Bundle bundle = getIntent().getExtras();
strGetPath = bundle.getString(GlobalClass.notesPath);
filePath = new File(strGetPath);
fileInputStream = new FileInputStream(filePath);
int size = fileInputStream.available();
bytbuffer = new byte[size];
fileInputStream.read(bytbuffer);
fileInputStream.close();
String strGetData = new String(bytbuffer);
Spanned spanHTMLData = AppHtml.fromHtml(strGetData);
LogM.e("===== Getting Data =====" + strGetData);
edtNoteDescription.setText(spanHTMLData);
The same Problem I faced while creating a project of saving the HTML notes.
As you said that you have made your customized HTML.java class I also customized the same class for my purpose.
The default Html.java class does not contain the functionality for backgroundcolor, font size, bullet etc.
So Here i am sharing the content of that class so you can get the idea from it to set the Backround Color to you HTML Note.
Let's assume your customized Html.java = AppHtml.java so other can understand it better.
1) First add the background color tag in your AppHtml.java class. Add this below code to your withinParagraph method.
if (style[j] instanceof BackgroundColorSpan) {
out.append("<font style = \"background-color:#");
String color = Integer
.toHexString(((BackgroundColorSpan) style[j])
.getBackgroundColor() + 0x01000000);
while (color.length() < 6) {
color = "0" + color;
}
out.append(color);
out.append("\">");
}
then complete the font style that you have started
if (style[j] instanceof BackgroundColorSpan) {
out.append("</font>");
}
2) Here is my Font class
private static class Font {
public String mColor;
public String mFace;
public String mbgColor;
public String mSize;
public Font(String color, String face, String bgColor, String size) {
mColor = color;
mFace = face;
mbgColor = bgColor;
mSize = size;
}
}
3) Here is my startFont method.
private static void startFont(SpannableStringBuilder text,
Attributes attributes) {
String color = attributes.getValue("", "color");
String face = attributes.getValue("", "face");
String bgColor = attributes.getValue("", "style");
String size = attributes.getValue("", "size");
int len = text.length();
text.setSpan(new Font(color, face, bgColor, size), len, len,
Spannable.SPAN_MARK_MARK);
}
4) Here is my endFont method.
private static void endFont(SpannableStringBuilder text) {
int len = text.length();
Object obj = getLast(text, Font.class);
int where = text.getSpanStart(obj);
text.removeSpan(obj);
if (where != len) {
Font f = (Font) obj;
if (f.mColor != null) {
if (!TextUtils.isEmpty(f.mColor)) {
if (f.mColor.startsWith("#")) {
Resources res = Resources.getSystem();
String name = f.mColor.substring(1);
int colorRes = res.getIdentifier(name, "color",
"android");
if (colorRes != 0) {
ColorStateList colors = res
.getColorStateList(colorRes);
text.setSpan(new TextAppearanceSpan(null, 0, 0,
colors, null), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
int c = getHtmlColor(f.mColor);
if (c != -1) {
text.setSpan(
new ForegroundColorSpan(c | 0xFF000000),
where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
if (f.mFace != null) {
text.setSpan(new TypefaceSpan(f.mFace), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (f.mbgColor != null) {
String bg_COLOR = f.mbgColor.substring(
f.mbgColor.lastIndexOf("#"), f.mbgColor.length());
if (!TextUtils.isEmpty(bg_COLOR)) {
if (bg_COLOR.startsWith("#")) {
Resources res = Resources.getSystem();
String name = bg_COLOR.substring(1);
int colorRes = res.getIdentifier(name, "color",
"android");
if (colorRes != 0) {
ColorStateList colors = res
.getColorStateList(colorRes);
text.setSpan(new TextAppearanceSpan(null, 0, 0,
colors, null), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
int c = getHtmlColor(bg_COLOR);
if (c != -1) {
text.setSpan(
new BackgroundColorSpan(c | 0xFF000000),
where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
if (f.mSize != null) {
if (!TextUtils.isEmpty(f.mSize)) {
int size = Integer.parseInt(f.mSize);
text.setSpan((new AbsoluteSizeSpan(size)), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
I do not know what is AppHtml in your source code but for Html<->Spannable parsing in Android we use android.text.Html class. This class though doesn't support BackgroundColorSpan for some reason.
I would suggest experimenting with Html.fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) method and pass a TagHandler to support what you need. Docs are here.
Create your own custom format and save it to a file
I don't know whether this is exactly what you are looking for, but I don't see the point to use HTML here. As other answers pointed out, fromHtml() is quite limited and using you custom file format will solve your saving / restoring problem in no time.
You need to store on a file the following pieces information:
How many spans you have
start, end, color for each span
your text
The following code snippet shows how to implement the store and load methods. It uses the Spanned text in a TextView. The complete Eclipse project took me about half an hour and can be found here.
public void store(View v)
{
Spanned s = (Spanned) mTextView.getText();
BackgroundColorSpan[] spans = s.getSpans(0, s.length(), BackgroundColorSpan.class);
BufferedWriter bw = null;
try
{
int len = spans.length;
bw = new BufferedWriter(new FileWriter(mFile));
bw.write(String.valueOf(len));
bw.newLine();
for (BackgroundColorSpan span : spans)
{
int start = s.getSpanStart(span);
int end = s.getSpanEnd(span);
int color = span.getBackgroundColor();
bw.write("" + start + "," + end + "," + color);
bw.newLine();
}
bw.write(mText);
clear(v);
}
catch (IOException e)
{
Log.e(TAG, "IO error", e);
}
finally
{
closeQuietly(bw);
}
}
public void load(View v)
{
BufferedReader br = null;
try
{
br = new BufferedReader(new FileReader(mFile));
int len = Integer.parseInt(br.readLine());
BackgroundColorSpan[] spans = new BackgroundColorSpan[len];
int[] starts = new int[len];
int[] ends = new int[len];
for (int i = 0; i < len; i++)
{
String[] tokens = br.readLine().split(",");
starts[i] = Integer.parseInt(tokens[0]);
ends[i] = Integer.parseInt(tokens[1]);
int color = Integer.parseInt(tokens[2]);
spans[i] = new BackgroundColorSpan(color);
}
mText = br.readLine();
SpannableString s = new SpannableString(mText);
for (int i = 0; i < len; i++)
{
s.setSpan(spans[i], starts[i], ends[i], Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
mTextView.setText(s);
}
catch (IOException e)
{
Log.e(TAG, "IO error", e);
}
finally
{
closeQuietly(br);
}
}
Current implementation of Html.fromHtml only supports Font's color and typeface attributes. You can check the same here.
As you said you are using modified version of Html, it is much more easier to support additional attributes by yourself.
While parsing the XML, when font open tag is encountered, all the font attribute values are stored in Font private class. When font close tag is encountered, for each of those attributes, apply corresponding span styles.
Step 1 : In Font private class add a data member for holding the background color and modify the constructor to accept additional paramter.
private static class Font {
...
public String mBackgroundColor; // Data member to hold background color
public Font(String color, String face, String backgroundColor) {
...
mBackgroundColor = backgroundColor;
}
...
}
Step 2 : In startFont method, handle the background-color attribute.
private static void startFont(SpannableStringBuilder text,
Attributes attributes) {
...
String backgroundColor = null;
// In this specific example, background-color attribute is present in style attribute.
String style = attributes.getValue("", "style");
if(style != null && style.contains("background-color")) {
String[] array = style.split(":");
if(array.length == 2)
backgroundColor = array[1];
} else {
// If background-color is specified as an attribute itself use this
backgroundColor = attributes.getValue("", "background-color");
}
// Pass the background-color to the Font constructor
text.setSpan(new Font(color, face, backgroundColor), len, len, Spannable.SPAN_MARK_MARK);
}
Step 3: In endFont method, add the BackgroundColorSpan to the text.
private static void endFont(SpannableStringBuilder text) {
...
if(f.mBackgroundColor != null) {
text.setSpan(new BackgroundColorSpan(Color.parseColor(f.mBackgroundColor)), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
P.S. : Since Html constructor is private, specializing the Html is not possible.
Unfortunately fromHtml() is pretty limited and it can't parse the font style attribute.
A rather old post in Commonware's blog has the list of the working attributes.
You have a few options:
Set the background spans by hand
Create your own parser that supports this and share it with the public (yay)
Use a WebView
Now using a WebView seems like the most straighforward solution, so here's an example:
WebView webView = (WebView) findViewById(R.id.webView);
String strGetData = "<p><font color =\"#7dff00\">This</font> <font color =\"#ff5100\">Is</font> a <font color =\"#04ff00\"><b><font style = \"background-color:#2929dd\">String</font></b></font>... </p>\n";
webView.loadData(strGetData, "text/html", "utf-8");
Is it possible to parse HTML code in a verbatim mode or something similar so that the source code fragments that eventually may appear (enclosed between pre and code HTML tags) can be displayed properly?
What I want to do is show source code in a user-friendly mode (easy to distinguish from the rest of the text, keep indentation, etc.), as Stack Overflow does :)
It seems that Html.fromHtml() supports only a reduced subset of HTML tags.
TextView will never succeed supporting all the html formating and styling you would want it to. Use WebView instead.
TextView is native and more lightweight, but exactly because of its lightweightedness it will not understand some of the directives you describe.
Finally I preparsed by myself the HTML code received, since Html.fromHtml does not support the pre and code tags, y replaced them with my custom format and pre-parsed the code inside those tags replacing "\n" with <br/> and " " with .
Then I send the results to Html.fromHtml, and the result is just fine:
public class HtmlParser {
public static Spanned parse(String text) {
if (text == null) return null;
text = parseSourceCode(text);
Spanned textSpanned = Html.fromHtml(text);
return textSpanned;
}
private static String parseSourceCode(String text) {
if (text.indexOf(ORIGINAL_PATTERN_BEGIN) < 0) return text;
StringBuilder result = new StringBuilder();
int begin;
int end;
int beginIndexToProcess = 0;
while (text.indexOf(ORIGINAL_PATTERN_BEGIN) >= 0) {
begin = text.indexOf(ORIGINAL_PATTERN_BEGIN);
end = text.indexOf(ORIGINAL_PATTERN_END);
String code = parseCodeSegment(text, begin, end);
result.append(text.substring(beginIndexToProcess, begin));
result.append(PARSED_PATTERN_BEGIN);
result.append(code);
result.append(PARSED_PATTERN_END);
//replace in the original text to find the next appearance
text = text.replaceFirst(ORIGINAL_PATTERN_BEGIN, PARSED_PATTERN_BEGIN);
text = text.replaceFirst(ORIGINAL_PATTERN_END, PARSED_PATTERN_END);
//update the string index to process
beginIndexToProcess = text.lastIndexOf(PARSED_PATTERN_END) + PARSED_PATTERN_END.length();
}
//add the rest of the string
result.append(text.substring(beginIndexToProcess, text.length()));
return result.toString();
}
private static String parseCodeSegment(String text, int begin, int end) {
String code = text.substring(begin + ORIGINAL_PATTERN_BEGIN.length(), end);
code = code.replace(" ", " ");
code = code.replace("\n","<br/>");
return code;
}
private static final String ORIGINAL_PATTERN_BEGIN = "<pre><code>";
private static final String ORIGINAL_PATTERN_END = "</code></pre>";
private static final String PARSED_PATTERN_BEGIN = "<font color=\"#888888\"><tt>";
private static final String PARSED_PATTERN_END = "</tt></font>";
}
I am developing a Calculator app and I want to display symbols like + - / * etc on a different color. Im using a TextView as my display.
I was able to do it when the buttons are being pressed with a code like this
coloredOperator = "<font color=#BED505>"+buttonPressed+"</font>";
textView.append(Html.fromHtml(coloredOperator));
However then I implemented a code on text change to order the operations when a new line is created on my textView, which looks something like this:
public void onTextChanged(CharSequence s, int start, int before, int count){
String message = s.toString();
// I have a java class that takes cares of this
int lastPositionOfBreakCharacter = getLastIndexOfRegex(message, "\\-|\\+|\\/|\\*|\\^");
int length = s.length();
int breakPosition = length-lastPositionOfBreakCharacter;
String text_view_text=t.getText().toString();
StringBuffer sb=new StringBuffer(text_view_text);
// So a new line is inserted before the last character +|-|* etc...
sb.insert(breakPosition,"\n");
textView.setText(sb);
}
The problem is that obviously this last functions strips my text view of all Spannables thus loosing style.
Is there any way to parse the text to find for the special characters, add the corresponding Spannables and then use .setText()?
Or do you have any other ideas on how to achieve what I'm after to?
Thanks!!!
How to use SpannableString with Regex in android?
The Correct answer works for this question.
////////////
public class SpanTest extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String dispStr = "This has the string ABCDEF in it \nSo does this :ABCDEF - see!\nAnd again ABCD here";
TextView tv = (TextView) findViewById(R.id.textView1);
tv.setText(dispStr);
changeTextinView(tv, "ABC", Color.RED);
}
private void changeTextinView(TextView tv, String target, int colour) {
String vString = (String) tv.getText();
int startSpan = 0, endSpan = 0;
Spannable spanRange = new SpannableString(vString);
while (true) {
startSpan = vString.indexOf(target, endSpan);
ForegroundColorSpan foreColour = new ForegroundColorSpan(colour);
// Need a NEW span object every loop, else it just moves the span
if (startSpan < 0)
break;
endSpan = startSpan + target.length();
spanRange.setSpan(foreColour, startSpan, endSpan,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
tv.setText(spanRange);
}
}
text = text + CepVizyon.getPhoneCode() + "\n\n"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText();
activationText.setText(text);
myTextView.setText(text);
I want to change color for CepVizyon.getPhoneCode()'s string. How can I do this?
Spannable is more flexible:
String text2 = text + CepVizyon.getPhoneCode() + "\n\n"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText();
Spannable spannable = new SpannableString(text2);
spannable.setSpan(new ForegroundColorSpan(Color.WHITE), text.length(), (text + CepVizyon.getPhoneCode()).length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
myTextView.setText(spannable, TextView.BufferType.SPANNABLE);
If you have static text that needs color, you can add it without any code via the strings file:
<string name="already_have_an_account">Already have an account? <font color='#01C6DB'>Login</font></string>
then
<TextView
android:layout_width="wrap_content"
android:layout_height="64dp"
android:text="#string/already_have_an_account"/>
result
I'm not sure which API versions this works on, but it doesn't work for API 19 that I've tested so far, so probably only some of the most recent API versions support this.
As #hairraisin mentioned in the comments, try using fgcolor instead of color for the font color, then it should work for lower API levels, but need more testing to be sure.
myTextView.setText(Html.fromHtml(text + "<font color=white>" + CepVizyon.getPhoneCode() + "</font><br><br>"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText()));
Here solution in Kotlin that uses SpannableString to change color of part of a string.
val phoneCodeColor = ContextCompat.getColor(this, R.color.myColor)
val text = SpannableStringBuilder()
.color(phoneCodeColor) { append("${ CepVizyon.getPhoneCode() }") }
.append("\n\n")
.append(getString(R.string.currentversion))
.append(${ CepVizyon.getLicenseText() })
activationText.text = text
myTextView.text = text
With regards to Maneesh's answer, this will work but you need to add and escape the quotes for the color attribute.
myTextView.setText(Html.fromHtml(text + "<font color=\"#FFFFFF\">" + CepVizyon.getPhoneCode() + "</font><br><br>"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText()));
It is good for me!
Spannable spannable = new SpannableString("ABC In-Network DEF");
String str = spannable.toString();
iStart = str.indexOf("In-Network");
iEnd = iStart + 10;/*10 characters = in-network. */
SpannableString ssText = new SpannableString(spannable);
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View widget) {
//your code at here.
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(true);
ds.setColor(getResources().getColor(R.color.green));
}
};
ssText.setSpan(clickableSpan, iStart, iEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.setText(ssText);
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
mTextView.setHighlightColor(Color.TRANSPARENT);
mTextView.setEnabled(true);
Here's a colorize function based on andyboot's answer:
/**
* Colorize a specific substring in a string for TextView. Use it like this: <pre>
* textView.setText(
* Strings.colorized("The some words are black some are the default.","black", Color.BLACK),
* TextView.BufferType.SPANNABLE
* );
* </pre>
* #param text Text that contains a substring to colorize
* #param word The substring to colorize
* #param argb The color
* #return the Spannable for TextView's consumption
*/
public static Spannable colorized(final String text, final String word, final int argb) {
final Spannable spannable = new SpannableString(text);
int substringStart=0;
int start;
while((start=text.indexOf(word,substringStart))>=0){
spannable.setSpan(
new ForegroundColorSpan(argb),start,start+word.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
substringStart = start+word.length();
}
return spannable;
}
I have made this little function, just pass in your text to color, the start and end indexes of what you want to color of that text and the color itself
Kotlin
private fun colorMyText(inputText:String,startIndex:Int,endIndex:Int,textColor:Int):Spannable{
val outPutColoredText: Spannable = SpannableString(inputText)
outPutColoredText.setSpan(
ForegroundColorSpan(textColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return outPutColoredText
}
Usage
txt_comment.text = colorMyText("Comentario: ${item.comentario}",0,13,Color.BLACK)
Use character escapes + Html.fromHtml()
How to store the String in the string resource folder
<string name="textFromRes">
<font color="#FF0000">This is colored in red </font> This is not
</string>
How to show in TextView?
String text = this.getResources().getString(R.string.textFromRes);
htmlText.setText(Html.fromHtml(text));
Bonus:
The String in the output looks like this
<string name="textFromRes">
<font color="#FF0000">This is colored in red </font> This is not
<br />
<h1> This is h1 heading </h1>
<br />
<h3> This is h2 subheading</h3>
<br />
<b> This text is bold</b>
<br />
<i> This text is italic</i>
<br />
Android users expect your app to look and behave in a way that is
consistent with the platform. Not only should you follow material
design guidelines for visual and navigation patterns,
but you should also follow quality guidelines for compatibility,
performance, security, and more.
<br />
<br />
The following links provide everything you need to design a high quality Android app.
</string>
With a general-purpose Kotlin extension function, it would look like this:
/**
* Change the color of a part of the text contained in this textView
*
* #param subStringToColorize has to already be set in the textView's text
* #param colorResId
*/
fun TextView.colorize(subStringToColorize: String, #ColorRes colorResId: Int) {
val spannable: Spannable = SpannableString(text)
val startIndex = text.indexOf(subStringToColorize, startIndex = 0, ignoreCase = false)
val endIndex = startIndex + subStringToColorize.length
val color: Int = ContextCompat.getColor(context, colorResId)
if (startIndex != -1) {
spannable.setSpan(ForegroundColorSpan(color),
startIndex,
endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
setText(spannable, TextView.BufferType.SPANNABLE)
}
}
I didn't like the idea of doing this by code every time i want to color parts of the text which i have been doing a lot in all of my apps (and since in some case text is being set in runtime with different inline-defined colors) so i created my own MarkableTextView.
The idea was to:
Detect XML tags from string
Identify and match tag name
Extract and save attributes and position of text
Remove tag and keep content
Iterate through attributes and apply styles
Here's the process step by step:
First i needed a way to find XML tags in a given string and Regex did the trick..
<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\s+([^>]*))?>([^>][^<]*)</\1\s*>
For the above to match an XML tag it has to have the following criteria:
Valid tag name like <a> <a > <a-a> <a ..attrs..> but not < a> <1>
Closing tag that has a matching name like <a></a> but not <a></b>
Any content, since there's no need to style "nothing"
Now for the attributes we're going to use this one..
([a-zA-Z]+)\s*=\s*(['"])\s*([^'"]+?)\s*\2
It has the same concept and generally i didn't need to go far for both since the compiler will take care of the rest if anything goes out of format.
Now we need a class that can hold the extracted data:
public class MarkableSheet {
private String attributes;
private String content;
private int outset;
private int ending;
private int offset;
private int contentLength;
public MarkableSheet(String attributes, String content, int outset, int ending, int offset, int contentLength) {
this.attributes = attributes;
this.content = content;
this.outset = outset;
this.ending = ending;
this.offset = offset;
this.contentLength = contentLength;
}
public String getAttributes() {
return attributes;
}
public String getContent() {
return content;
}
public int getOutset() {
return outset;
}
public int getContentLength() {
return contentLength;
}
public int getEnding() {
return ending;
}
public int getOffset() {
return offset;
}
}
Before anything else, we're going to add this cool iterator that i've been using for long to loop through matches (can't remember the author):
public static Iterable<MatchResult> matches(final Pattern p, final CharSequence input) {
return new Iterable<MatchResult>() {
public Iterator<MatchResult> iterator() {
return new Iterator<MatchResult>() {
// Use a matcher internally.
final Matcher matcher = p.matcher(input);
// Keep a match around that supports any interleaving of hasNext/next calls.
MatchResult pending;
public boolean hasNext() {
// Lazily fill pending, and avoid calling find() multiple times if the
// clients call hasNext() repeatedly before sampling via next().
if (pending == null && matcher.find()) {
pending = matcher.toMatchResult();
}
return pending != null;
}
public MatchResult next() {
// Fill pending if necessary (as when clients call next() without
// checking hasNext()), throw if not possible.
if (!hasNext()) { throw new NoSuchElementException(); }
// Consume pending so next call to hasNext() does a find().
MatchResult next = pending;
pending = null;
return next;
}
/** Required to satisfy the interface, but unsupported. */
public void remove() { throw new UnsupportedOperationException(); }
};
}
};
}
MarkableTextView:
public class MarkableTextView extends AppCompatTextView {
public MarkableTextView(Context context) {
super(context);
}
public MarkableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MarkableTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void setText(CharSequence text, BufferType type) {
// Intercept and process text
text = prepareText(text.toString());
super.setText(text, type);
}
public Spannable Markable;
private Spannable prepareText(String text) {
String parcel = text;
Multimap<String, MarkableSheet> markableSheets = ArrayListMultimap.create();
// Used to correct content position after tossing tags
int totalOffset = 0;
// Iterate through text
for (MatchResult match : matches(Markable.Patterns.XML, parcel)) {
// Get tag name
String tag = match.group(1);
// Match with a defined tag name "case-sensitive"
if (!tag.equals(Markable.Tags.MARKABLE)) {
// Break if no match
break;
}
// Extract data
String attributes = match.group(2);
String content = match.group(3);
int outset = match.start(0);
int ending = match.end(0);
int offset = totalOffset; // offset=0 since no preceded changes happened
int contentLength = match.group(3).length();
// Calculate offset for the next element
totalOffset = (ending - outset) - contentLength;
// Add to markable sheets
MarkableSheet sheet =
new MarkableSheet(attributes, content, outset, ending, offset, contentLength);
markableSheets.put(tag, sheet);
// Toss the tag and keep content
Matcher reMatcher = Markable.Patterns.XML.matcher(parcel);
parcel = reMatcher.replaceFirst(content);
}
// Initialize spannable with the modified text
Markable = new SpannableString(parcel);
// Iterate through markable sheets
for (MarkableSheet sheet : markableSheets.values()) {
// Iterate through attributes
for (MatchResult match : matches(Markable.Patterns.ATTRIBUTES, sheet.getAttributes())) {
String attribute = match.group(1);
String value = match.group(3);
// Apply styles
stylate(attribute,
value,
sheet.getOutset(),
sheet.getOffset(),
sheet.getContentLength());
}
}
return Markable;
}
Finally, styling, so here's a very simple styler i made for this answer:
public void stylate(String attribute, String value, int outset, int offset, int length) {
// Correct position
outset -= offset;
length += outset;
if (attribute.equals(Markable.Tags.TEXT_STYLE)) {
if (value.contains(Markable.Tags.BOLD) && value.contains(Markable.Tags.ITALIC)) {
Markable.setSpan(
new StyleSpan(Typeface.BOLD_ITALIC),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (value.contains(Markable.Tags.BOLD)) {
Markable.setSpan(
new StyleSpan(Typeface.BOLD),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (value.contains(Markable.Tags.ITALIC)) {
Markable.setSpan(
new StyleSpan(Typeface.ITALIC),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (value.contains(Markable.Tags.UNDERLINE)) {
Markable.setSpan(
new UnderlineSpan(),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
if (attribute.equals(Markable.Tags.TEXT_COLOR)) {
if (value.equals(Markable.Tags.ATTENTION)) {
Markable.setSpan(
new ForegroundColorSpan(ContextCompat.getColor(
getContext(),
R.color.colorAttention)),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (value.equals(Markable.Tags.INTERACTION)) {
Markable.setSpan(
new ForegroundColorSpan(ContextCompat.getColor(
getContext(),
R.color.colorInteraction)),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
And here's how the Markable class containing the definitions looks like:
public class Markable {
public static class Patterns {
public static final Pattern XML =
Pattern.compile("<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\\s+([^>]*))?>([^>][^<]*)</\\1\\s*>");
public static final Pattern ATTRIBUTES =
Pattern.compile("(\\S+)\\s*=\\s*(['\"])\\s*(.+?)\\s*\\2");
}
public static class Tags {
public static final String MARKABLE = "markable";
public static final String TEXT_STYLE = "textStyle";
public static final String BOLD = "bold";
public static final String ITALIC = "italic";
public static final String UNDERLINE = "underline";
public static final String TEXT_COLOR = "textColor";
public static final String ATTENTION = "attention";
public static final String INTERACTION = "interaction";
}
}
All that we need now is to reference a string and basically it should look like this:
<string name="markable_string">
<![CDATA[Hello <markable textStyle=\"underline\" textColor=\"interaction\">world</markable>!]]>
</string>
Make sure to wrap the tags with a CDATA Section and escape " with \.
I made this as a modular solution to process parts of the text in all different ways without the need of stuffing unnecessary code behind.
I did as andy boot said, but i had a clickable span as well, and it didn't work because the order the setSpans were called. So you have to first call the spannable.setSpan(clickableSpanand... then the spannable.setSpan(new ForegroundColorSpan... to get the color in the TextView
Inspired by Alejandro H. Cruz's answer above.
His function only works for a single substring match, I have updated his method to use Regex and should update colors on all matches:
fun TextView.colorizeAll(subStringToColorize: String, #ColorRes colorResId: Int) {
val color: Int = ContextCompat.getColor(context, colorResId)
val spannable: Spannable = SpannableString(text)
val pattern = subStringToColorize.toRegex()
val matches = pattern.findAll(text, 0)
matches.forEach { match ->
val startIndex = match.range.first
val endIndex = match.range.last + match.range.step
spannable.setSpan(ForegroundColorSpan(color),
startIndex,
endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
setText(spannable, TextView.BufferType.SPANNABLE)
}
}
I created this little helper method that can be called from a TextView:
fun TextView.attributedString(
forText: String,
foregroundColor: Int? = null,
style: StyleSpan? = null
) {
val spannable: Spannable = SpannableString(text)
// check if the text we're highlighting is empty to abort
if (forText.isEmpty()) {
return
}
// compute the start and end indices from the text
val startIdx = text.indexOf(forText)
val endIdx = startIdx + forText.length
// if the indices are out of bounds, abort as well
if (startIdx < 0 || endIdx > text.length) {
return
}
// check if we can apply the foreground color
foregroundColor?.let {
spannable.setSpan(
ForegroundColorSpan(it),
startIdx,
endIdx,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
}
// check if we have a stylespan
style?.let {
spannable.setSpan(
style,
startIdx,
endIdx,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
}
// apply it
text = spannable
}
To use it:
plateText.text = "Hello world!"
// This will color the part "world" to whatever color you have defined
// And make the word **bold**.
plateText.attributedString(
"world",
getColor(R.color.colorMatchingText, null),
StyleSpan(Typeface.BOLD)
)
Tested on API 29, cheers!
SpannableStringBuilder builder = new SpannableStringBuilder();
String the = "The ";
SpannableString theSpannable= new SpannableString(the);
builder.append(theSpannable);
String author = "author ";
SpannableString authorSpannable= new SpannableString(author);
authorSpannable.setSpan(new RelativeSizeSpan(1.2f), 0,authorSpannable.length(), 0); // set size
authorSpannable.setSpan(new ForegroundColorSpan(Color.BLACK), 0, authorSpannable.length(), 0);
builder.append(authorSpannable);
String has = "has ";
SpannableString hasSpannable= new SpannableString(has);
builder.append(hasSpannable);
String approved = "approved ";
SpannableString approvedSpannable= new SpannableString(approved);
approvedSpannable.setSpan(new RelativeSizeSpan(1.2f), 0,approvedSpannable.length(), 0); // set size
StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
approvedSpannable.setSpan(boldSpan, 0, approvedSpannable.length() + 0, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
approvedSpannable.setSpan(new ForegroundColorSpan(ContextCompat.getColor(this, R.color.CLR_PRESSED_SAVED)), 0,
approvedSpannable.length(), 0);
builder.append(approvedSpannable);
String white = "your access to this share. Do you want re-access now?";
SpannableString whiteSpannable= new SpannableString(white);
builder.append(whiteSpannable);
_AccessStatusText.setText(builder, TextView.BufferType.SPANNABLE);
its Simple
String text = "We've sent the code to ";
String text2 = text + getEmail() + "\n\n";
Spannable spannable = new SpannableString(text2);
spannable.setSpan(new ForegroundColorSpan(Color.BLUE), text.length(), (text + getEmail()).length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mBinding.textViewStatus.setText(spannable, TextView.BufferType.SPANNABLE);
DO NOT USE Html.fromHtml to avoid fragmented behavior. Spannable or SpannableStringBuilder is the best way to do this with ForegroundColorSpan or BackgroundColorSpan depending on your requirements. Html or HtmlCompat tag even with style color or background-color is not realiable as it does not work on all SDK specially on lower version such as 21, same case for emojis.
Example case:
<span style=\"background-color:red\">⬆</span>
When the Html string above converts to Spanned using HtmlCompat.fromHtml and use it in setText() the style does not work on older SDK version.
One way is to split myTextView to few separate TextViews, one of which would be only for phone code. Then controlling color of this specific TextView is pretty straight-forward.