How can I change the color of a part of a TextView?

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?

129505 次浏览

一种方法是将 myTextView拆分为几个独立的 TextViews,其中一个仅用于电话代码。然后控制这个特定的 TextView的颜色是相当简单的。

myTextView.setText(Html.fromHtml(text + "<font color=white>" + CepVizyon.getPhoneCode() + "</font><br><br>"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText()));

关于 Maneesh 的回答,这将工作,但您需要添加和转义的颜色属性的引号。

myTextView.setText(Html.fromHtml(text + "<font color=\"#FFFFFF\">" + CepVizyon.getPhoneCode() + "</font><br><br>"
+ getText(R.string.currentversion) + CepVizyon.getLicenseText()));

Spannable 更加灵活:

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

这对我有好处!

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

如果您有需要颜色的静态文本,您可以通过字符串文件添加它,而不需要任何代码:

<string name="already_have_an_account">Already have an account? <font color='#01C6DB'>Login</font></string>

那么

<TextView
android:layout_width="wrap_content"
android:layout_height="64dp"
android:text="@string/already_have_an_account"/>

结果

enter image description here

我不确定这在哪个 API 版本上可以工作,但是到目前为止我测试过的 API 19并不支持,所以可能只有一些最新的 API 版本支持。

正如评论中提到的@hair raisin,尝试使用 fgcolor而不是 color作为字体颜色,那么它应该适用于较低的 API 级别,但需要更多的测试才能确定。

下面是一个基于 andyboot 的答案的 colorize函数:

 /**
* 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;
}

我不喜欢这样的想法,每次我想要颜色的文本部分,我已经做了很多在我的所有应用程序(因为在某些情况下,文本是在运行时设置不同的内联定义的颜色) ,所以我创建了自己的 MarkableTextView

他们的想法是:

  • 从字符串中检测 XML 标记
  • 识别并匹配标记名
  • 提取和保存文本的属性和位置
  • 删除标签并保留内容
  • 遍历属性并应用样式

下面是一步一步的过程:

首先,我需要一种在给定字符串中查找 XML 标记的方法,而 Regex做到了这一点。

<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\s+([^>]*))?>([^>][^<]*)</\1\s*>

为了使上述内容与 XML 标记匹配,它必须具备以下条件:

  • 有效的标记名称,如 <a> <a > <a-a> <a ..attrs..>,但不是 < a> <1>
  • 具有匹配名称(如 <a></a>,但不是 <a></b>)的结束标记
  • 任何内容,因为没有必要样式“无”

现在我们要使用这个属性。

([a-zA-Z]+)\s*=\s*(['"])\s*([^'"]+?)\s*\2

它有相同的概念,一般来说,我不需要走得太远,因为编译器会照顾其余的,如果有什么不符合格式。

现在我们需要一个可以保存提取数据的类:

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

在做其他事情之前,我们将添加这个很酷的迭代器,我已经使用了很长时间来循环遍历 match (不记得作者了) < strong > :

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

最后是造型,下面是我为这个问题设计的一个非常简单的造型:

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

下面是包含定义的 Markable类的样子:

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

我们现在需要做的就是引用一个字符串,基本上它应该是这样的:

<string name="markable_string">
<![CDATA[Hello <markable textStyle=\"underline\" textColor=\"interaction\">world</markable>!]]>
</string>

确保用 CDATA Section包装标签,用 \转义 "

我把它作为一个模块化的解决方案,以所有不同的方式处理文本的各个部分,而不需要在后面填充不必要的代码。

我照 Andy Boot 说的做了,但是我也有一个可点击的跨度,但是它没有工作,因为命令 setSpans被调用了。所以你必须先调用 spannable.setSpan(clickableSpanand...,然后调用 spannable.setSpan(new ForegroundColorSpan...,才能得到 TextView 中的颜色

这是 Kotlin 的解决方案,使用 SpannableString来改变字符串部分的颜色。

    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

通过一个通用的 Kotlin 扩展函数,它看起来像这样:

/**
* 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)
}
}

我已经做了这个小函数,只需要将文本传递到颜色,文本的起始和结束索引的颜色以及颜色本身

科特林

   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
}

用法

txt_comment.text = colorMyText("Comentario: ${item.comentario}",0,13,Color.BLACK)

使用字符转义 + Html.from Html ()

enter image description here

如何在字符串资源文件夹中存储字符串

<string name="textFromRes">
&lt;font color="#FF0000">This is colored in red &lt;/font> This is not
</string>

如何在 TextView 中显示?

String text = this.getResources().getString(R.string.textFromRes);
htmlText.setText(Html.fromHtml(text));

意外收获:

输出中的 String 如下所示

<string name="textFromRes">
&lt;font color="#FF0000">This is colored in red &lt;/font> This is not
&lt;br /&gt;
&lt;h1> This is h1 heading &lt;/h1>
&lt;br /&gt;
&lt;h3> This is h2 subheading&lt;/h3>
&lt;br /&gt;
&lt;b> This text is bold&lt;/b>
&lt;br /&gt;
&lt;i> This text is italic&lt;/i>
&lt;br /&gt;
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.
&lt;br /&gt;
&lt;br /&gt;
The following links provide everything you need to design a high quality Android app.
</string>

灵感来自 Alejandro H. Cruz 的答案。

他的函数只适用于一个子字符串匹配,我已经更新了他的方法来使用正则表达式,并且应该更新所有匹配的颜色:

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)


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

我创建了这个可以从 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
}

使用方法:

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

测试 API 29,干杯!

很简单

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

不要使用 Html.fromHtml来避免支离破碎的行为。SpannableSpannableStringBuilder是最好的方式做到这一点与 ForegroundColorSpanBackgroundColorSpan取决于您的要求。HtmlHtmlCompat标签甚至与风格 colorbackground-color是不可实现的,因为它不工作在所有的 SDK 特别是在较低的版本,如21,同样的情况下为表情符号。

例子:

<span style=\"background-color:red\">&#11014;</span>

当上面的 Html 字符串使用 HtmlCompat.fromHtml转换为 Spanned并在 setText()中使用它时,这种样式不适用于旧的 SDK 版本。