Come posso cambiare il colore di una parte di un TextView?


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

Voglio cambiare il colore della CepVizyon.getPhoneCode()stringa di. Come posso fare questo?




Questa domanda è stata posta il 19 luglio 10 alle 16:27, circa 3 mesi prima della tua. Tuttavia, non è sempre il post più vecchio che deve essere l'obiettivo duplicato. Si dovrebbe tenere conto del numero di visualizzazioni, del numero di voti, del numero di risposte e della chiarezza della domanda. Contrassegnandolo come duplicato, puoi aiutare le persone a trovare quelle altre risposte che rispondono anche alla tua domanda.
Suragch


Per capire veramente cosa c'è dietro le quinte, consiglio sempre di leggere un articolo di approfondimento come questo: medium.com/androiddevelopers/…
Michal Vician

Risposte:


170

Spannable è più flessibile:

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

3
Grazie per questa risposta! Questo è più simile a NSAttributedString in iOS :) Per essere ancora più flessibile, sostituisci text.lenght con text2.indexOf (CepVizyon.getPhoneCode ()) che ti permette di non conoscere la prima parte della stringa.
iGranDav

1
Dovresti mettere ()dopo text.lengthcome lengthè un metodo e non un campo. Lo farei da solo ma le modifiche devono essere di almeno 6 caratteri :)
MSX

Questa è di gran lunga la migliore risposta.
Pau Arlandis Martinez

1
Il problema con Spannable è che ellipsize = end non funziona più. Che in alcuni casi è un problema piuttosto serio.
Juan Carlos Ospina Gonzalez

1
Funziona benissimo. Sebbene sia consigliabile creare una stringa HTML. E poi analizzandolo tramite la 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()));

61

Se hai un testo statico che necessita di colore, puoi aggiungerlo senza codice tramite il file di stringhe:

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

poi

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

risultato

inserisci qui la descrizione dell'immagine

non sono sicuro su quale versione di API funzioni, ma non funziona per l'API 19 che è stato testato finora, quindi probabilmente solo alcune delle versioni API più recenti lo supportano

modifica: come @hairraisin menzionato nei commenti, prova a utilizzare al fgcolorposto del colorcolore del carattere, quindi dovrebbe funzionare per livelli API inferiori, ma per essere sicuro sono necessari ulteriori test


3
Ho testato con successo utilizzando <font fgcolor=...API 15 e API 25 (non ho testato specificamente 19 però)
hair raisin

Non funziona quando imposto il testo a livello di programmazione. :(
Rohit Singh

Questa non è una soluzione ideale, perché mescola le traduzioni con i colori del testo.
Miloš Černilovský

16

Per quanto riguarda la risposta di Maneesh, funzionerà ma è necessario aggiungere e sfuggire alle virgolette per l'attributo color.

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

8

È un bene per 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);

6

Ecco la soluzione in Kotlin che usa SpannableStringper cambiare il colore di una parte di una stringa.

    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

1
Grazie. Soluzione semplicemente elegante per Kotlin.
Nhon Nguyen

5

Ecco una colorizefunzione basata sulla risposta di 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;
}

4

Non mi piaceva l'idea di farlo tramite codice ogni volta che voglio colorare parti del testo che ho fatto molto in tutte le mie app (e poiché in alcuni casi il testo viene impostato in runtime con inline diversi- colori definiti) quindi ho creato il mio MarkableTextView.

L'idea era di:

  • Rileva i tag XML dalla stringa
  • Identifica e abbina il nome del tag
  • Estrai e salva attributi e posizione del testo
  • Rimuovi tag e conserva il contenuto
  • Scorri gli attributi e applica gli stili

Ecco il processo passo dopo passo:

Per prima cosa avevo bisogno di un modo per trovare i tag XML in una determinata stringa e Regexho fatto il trucco.

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

Affinché quanto sopra corrisponda a un tag XML, deve avere i seguenti criteri:

  • Nome tag valido come <a> <a > <a-a> <a ..attrs..>ma non< a> <1>
  • Tag di chiusura che ha un nome corrispondente come <a></a>ma non<a></b>
  • Qualsiasi contenuto, poiché non è necessario applicare uno stile "niente"

Ora per gli attributi useremo questo ..

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

Ha lo stesso concetto e generalmente non ho bisogno di andare lontano per entrambi poiché il compilatore si prenderà cura del resto se qualcosa va fuori formato.

Ora abbiamo bisogno di una classe che possa contenere i dati estratti:

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

Prima di ogni altra cosa, aggiungeremo questo fantastico iteratore che ho usato a lungo per scorrere le partite (non ricordo l'autore) :

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

Infine, lo styling, quindi ecco uno styler molto semplice che ho creato per questa risposta:

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

Ed ecco come Markableappare la classe contenente le definizioni:

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

Tutto ciò di cui abbiamo bisogno ora è fare riferimento a una stringa e fondamentalmente dovrebbe assomigliare a questo:

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

Assicurati di racchiudere i tag con una CDATA Sectione di uscire "con\ .

L'ho creata come soluzione modulare per elaborare parti del testo in tutti i modi diversi senza la necessità di riempire dietro codice non necessario.


4

Ho fatto come ha detto andy boot, ma avevo anche un intervallo cliccabile e non ha funzionato perché l'ordine è setSpansstato chiamato. Quindi devi prima chiamare il spannable.setSpan(clickableSpanand...quindi il spannable.setSpan(new ForegroundColorSpan...per ottenere il colore nel TextView


4

Ho creato questa piccola funzione, passa semplicemente nel tuo testo al colore, gli indici di inizio e fine di ciò che vuoi colorare di quel testo e il colore stesso

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
        }

Utilizzo

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

2

Con una funzione di estensione Kotlin per uso generale, sarebbe simile a questo:

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

1

Usa caratteri di escape + Html.fromHtml ()

inserisci qui la descrizione dell'immagine

Come memorizzare la stringa nella cartella delle risorse della stringa

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

Come mostrare in TextView?

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

Bonus:

La stringa nell'output ha questo aspetto

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

-5

Un modo è dividere myTextViewin pochi separati TextViews, uno dei quali sarebbe solo per il codice telefonico. Quindi controllare il colore di questo specifico TextViewè piuttosto semplice.


7
Nah, rompicoglioni. Usare uno spannabile è il modo giusto.
Marc DiMillo

La classe Spannable può farlo senza dividere
Sz-Nika Janos
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.