Como posso alterar a cor de uma parte de um TextView?

103
text = text + CepVizyon.getPhoneCode() + "\n\n"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText();
    activationText.setText(text);   
myTextView.setText(text);

Quero mudar a cor CepVizyon.getPhoneCode()do barbante de. Como posso fazer isso?

atasoyh
fonte
Possível duplicata de Set color of TextView span no Android
Suragch
Essa pergunta foi feita em 19 de julho de 2010 às 16:27, cerca de 3 meses antes da sua. No entanto, nem sempre é a postagem mais antiga que precisa ser o destino duplicado. O número de pontos de vista, número de votos, número de respostas e clareza da pergunta devem ser levados em consideração. Ao marcar isso como uma duplicata, pode ajudar as pessoas a encontrar outras respostas que também respondem à sua pergunta.
Suragch
Verifique stackoverflow.com/a/57089362/6667442
Ketan Ramani
Para realmente entender o que está por trás dos bastidores, eu sempre sugiro ler um artigo detalhado como este: medium.com/androiddevelopers/…
Michal Vician

Respostas:

170

Spannable é mais flexível:

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);
andy boot
fonte
3
Obrigado por esta resposta! Isso é mais parecido com NSAttributedString no iOS :) Para ser ainda mais flexível, substitua text.lenght por text2.indexOf (CepVizyon.getPhoneCode ()) que permite que você não saiba a primeira parte da String.
iGranDav
1
Você deve colocar ()depois de text.lengthcomo lengthé um método, não um campo. Eu faria isso sozinho, mas as edições devem ter pelo menos 6 caracteres :)
MSX
Esta é a melhor resposta de longe.
Pau Arlandis Martinez
1
O problema com o Spannable é que ellipsize = end não funciona mais. O que é um problema muito sério em alguns casos.
Juan Carlos Ospina Gonzalez
1
Funciona muito bem. Embora seja aconselhável criar uma string HTML. E então analisando-o através da classe HTML. Html.fromHtml(R.id.your_html_string);
sud007
72
myTextView.setText(Html.fromHtml(text + "<font color=white>" + CepVizyon.getPhoneCode() + "</font><br><br>"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText()));
Maneesh
fonte
61

Se você tiver texto estático que precisa de cor, pode adicioná-lo sem nenhum código por meio do arquivo de strings:

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

então

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

resultado

insira a descrição da imagem aqui

não tenho certeza em quais versões da API isso funciona, mas não funciona para a API 19 que eu testei até agora, então provavelmente apenas algumas das versões mais recentes da API suportam isso

editar: como @hairraisin mencionado nos comentários, tente usar em fgcolorvez de colorpara a cor da fonte, então deve funcionar para níveis de API inferiores, mas precisa de mais testes para ter certeza

Fonix
fonte
3
Eu testei com sucesso usando <font fgcolor=...API 15 e API 25 (eu não testei especificamente 19 embora)
passa de cabelo em
Não funciona quando defino o texto de forma programática. :(
Rohit Singh
Esta não é uma solução ideal, pois mistura traduções com cores de texto.
Miloš Černilovský 01 de
16

Com relação à resposta de Maneesh, isso funcionará, mas você precisa adicionar e escapar as aspas para o atributo de cor.

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

Isso é bom para mim!

            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);
Anh Duy
fonte
6

Aqui está solução em Kotlin que usa SpannableStringpara mudar a cor de parte de uma 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
Dmitrii Leonov
fonte
1
Obrigado. Solução simplesmente elegante para Kotlin.
Nhon Nguyen
5

Esta é uma colorizefunção baseada na resposta de andyboot:

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

Não gostei da ideia de fazer isso por código toda vez que quero colorir partes do texto, o que tenho feito muito em todos os meus aplicativos (e já que em alguns casos o texto está sendo definido em tempo de execução com diferentes inline- cores definidas), então eu criei o meu próprio MarkableTextView.

A ideia era:

  • Detectar tags XML da string
  • Identifique e corresponda ao nome da tag
  • Extraia e salve atributos e posição de texto
  • Remova a tag e mantenha o conteúdo
  • Repita os atributos e aplique estilos

Aqui está o processo passo a passo:

Primeiro, eu precisava encontrar uma maneira de encontrar tags XML em uma determinada string e Regexfiz o truque.

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

Para que o acima corresponda a uma tag XML, ele deve ter os seguintes critérios:

  • Nome de tag válido como, <a> <a > <a-a> <a ..attrs..>mas não< a> <1>
  • Tag de fechamento que tem um nome correspondente, <a></a>mas não<a></b>
  • Qualquer conteúdo, já que não há necessidade de estilizar "nada"

Agora, para os atributos, vamos usar este ..

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

Ele tem o mesmo conceito e geralmente não preciso ir muito longe para ambos, já que o compilador cuidará do resto se alguma coisa sair do formato.

Agora precisamos de uma classe que possa conter os dados extraídos:

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

Antes de mais nada, vamos adicionar este iterador legal que tenho usado por muito tempo para fazer um loop nas correspondências (não me lembro do autor) :

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

Finalmente, estilo, então aqui está um estilizador muito simples que fiz para esta resposta:

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

E esta é a aparência da Markableclasse que contém as definições:

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

Tudo o que precisamos agora é fazer referência a uma string e, basicamente, ela deve se parecer com isto:

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

Certifique-se de envolver as tags com a CDATA Sectione escapar "com \.

Fiz isso como uma solução modular para processar partes do texto de todas as maneiras diferentes, sem a necessidade de colocar código desnecessário por trás.

Explisam
fonte
4

Fiz o que o andy boot disse, mas também tinha um span clicável e não funcionou porque o pedido setSpansfoi chamado. Então você tem que primeiro chamar o spannable.setSpan(clickableSpanand...então o spannable.setSpan(new ForegroundColorSpan...para obter a cor no TextView

Tincho825
fonte
4

Fiz esta pequena função, basta passar o seu texto para colorir, os índices inicial e final do que você deseja colorir desse texto e a própria cor

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
        }

Uso

txt_comment.text = colorMyText("Comentario: ${item.comentario}",0,13,Color.BLACK)
Gastón Saillén
fonte
2

Com uma função de extensão Kotlin de uso geral, seria assim:

/**
 * 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 = if (/* Your code for isMarshmallowOrUp() */ ) {
      this.context.getColor(colorResId)
  } else {
      this.context.resources.getColor(colorResId)
  }

  spannable.setSpan(ForegroundColorSpan(color),
                  startIndex,
                  endIndex,
                  Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

  this.setText(spannable, TextView.BufferType.SPANNABLE)
}
Alejandro H. Cruz
fonte
1

Use escapes de caracteres + Html.fromHtml ()

insira a descrição da imagem aqui

Como armazenar a string na pasta de recursos da string

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

Como mostrar no TextView?

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

Bônus:

A string na saída se parece com isto

<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>
Rohit Singh
fonte
-5

Uma maneira é dividir myTextViewem alguns separados TextViews, um dos quais seria apenas para o código do telefone. Então, controlar a cor deste específico TextViewé bastante simples.

Ralkie
fonte
7
Nah, um pé no saco. Usar um spannable é o caminho certo.
Marc DiMillo de
A classe Spannable pode fazer isso sem se dividir
Sz-Nika Janos