Comment puis-je changer la couleur d'une partie d'un TextView?

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

Je veux changer la couleur de CepVizyon.getPhoneCode()la chaîne de. Comment puis-je faire ceci?

atasoyh
la source
Double
Suragch
Cette question a été posée le 19 juillet 2010 à 16:27, environ 3 mois avant la vôtre. Cependant, ce n'est pas toujours le post le plus ancien qui doit être la cible en double. Le nombre de vues, le nombre de votes, le nombre de réponses et la clarté de la question doivent être pris en compte. En le marquant comme un doublon, cela peut aider les gens à trouver les autres réponses qui répondent également à votre question.
Suragch
Vérifiez ce stackoverflow.com/a/57089362/6667442
Ketan Ramani
Pour vraiment comprendre les coulisses, je suggère toujours de lire un article en profondeur comme celui-ci: medium.com/androiddevelopers
Michal Vician

Réponses:

171

Spannable est plus 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);
Andy boot
la source
3
Merci pour cette réponse! Cela ressemble plus à NSAttributedString sous iOS :) Pour être encore plus flexible, remplacez text.lenght par text2.indexOf (CepVizyon.getPhoneCode ()) qui vous permet de ne pas connaître la première partie de la chaîne.
iGranDav
1
Vous devriez mettre ()après text.lengthcomme lengthune méthode pas un champ. Je le ferais moi-même mais les modifications doivent être d'au moins 6 caractères :)
MSX
C'est de loin la meilleure réponse.
Pau Arlandis Martinez
1
Le problème avec Spannable est que ellipsize = end ne fonctionne plus. Ce qui est un problème assez grave dans certains cas.
Juan Carlos Ospina Gonzalez
1
Fonctionne très bien. Bien que la création d'une chaîne HTML soit recommandée. Et puis l'analyser via la classe HTML. Html.fromHtml(R.id.your_html_string);
sud007
73
myTextView.setText(Html.fromHtml(text + "<font color=white>" + CepVizyon.getPhoneCode() + "</font><br><br>"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText()));
Maneesh
la source
63

Si vous avez du texte statique qui a besoin de couleur, vous pouvez l'ajouter sans aucun code via le fichier de chaînes:

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

puis

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

résultat

entrez la description de l'image ici

Je ne sais pas sur quelles versions d'API cela fonctionne, mais ne fonctionne pas pour l'API 19 que j'ai testée jusqu'à présent, donc probablement seulement certaines des versions d'API les plus récentes prennent en charge cela

edit: comme @hairraisin mentionné dans les commentaires, essayez d'utiliser fgcolorau lieu de colorpour la couleur de police, alors cela devrait fonctionner pour les niveaux d'API inférieurs, mais nécessite plus de tests pour être sûr

Fonix
la source
3
J'ai testé avec succès en utilisant l' <font fgcolor=...API 15 et l'API 25 (je n'ai cependant pas testé spécifiquement 19)
hair raisin
Ne fonctionne pas lorsque je définis le texte par programme. :(
Rohit Singh
Ce n'est pas une solution idéale, car elle mélange les traductions avec les couleurs du texte.
Miloš Černilovský le
16

En ce qui concerne la réponse de Maneesh, cela fonctionnera, mais vous devez ajouter et échapper les guillemets pour l'attribut de couleur.

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

C'est bon pour moi!

            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
la source
6

Voici une colorizefonction basée sur la réponse d'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
la source
6

Voici la solution dans Kotlin qui utilise SpannableStringpour changer la couleur d'une partie d'une chaîne.

    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
la source
1
Je vous remercie. Solution simplement élégante pour Kotlin.
Nhon Nguyen
4

Je n'aimais pas l'idée de faire cela par code chaque fois que je veux colorer des parties du texte que j'ai beaucoup faites dans toutes mes applications (et puisque dans certains cas, le texte est défini en runtime avec différents en ligne- couleurs définies) alors j'ai créé les miennes MarkableTextView.

L'idée était de:

  • Détecter les balises XML de la chaîne
  • Identifier et faire correspondre le nom du tag
  • Extraire et enregistrer les attributs et la position du texte
  • Supprimer la balise et conserver le contenu
  • Itérer les attributs et appliquer des styles

Voici le processus étape par étape:

J'avais d'abord besoin d'un moyen de trouver des balises XML dans une chaîne donnée et j'ai Regexfait l'affaire.

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

Pour que ce qui précède corresponde à une balise XML, il doit avoir les critères suivants:

  • Nom de balise valide comme <a> <a > <a-a> <a ..attrs..>mais pas< a> <1>
  • Balise de fermeture qui a un nom correspondant comme <a></a>mais pas<a></b>
  • N'importe quel contenu, car il n'est pas nécessaire de nommer «rien»

Maintenant, pour les attributs, nous allons utiliser celui-ci.

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

Il a le même concept et généralement je n'ai pas eu besoin d'aller loin pour les deux puisque le compilateur se chargera du reste si quelque chose ne va pas.

Nous avons maintenant besoin d'une classe qui peut contenir les données extraites:

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

Avant toute chose, nous allons ajouter cet itérateur sympa que j'utilise depuis longtemps pour parcourir les matchs ( je ne me souviens plus de l'auteur) :

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

Enfin, le style, voici donc un styler très simple que j'ai créé pour cette réponse:

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

Et voici à quoi Markableressemble la classe contenant les définitions:

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

Tout ce dont nous avons besoin maintenant est de référencer une chaîne et, en gros, cela devrait ressembler à ceci:

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

Assurez-vous d'envelopper les balises avec un CDATA Sectionet d'échapper "avec \.

J'ai fait cela comme une solution modulaire pour traiter des parties du texte de toutes les manières différentes sans avoir besoin de bourrer du code inutile.

Explisam
la source
4

J'ai fait comme Andy Boot l'a dit, mais j'avais aussi une durée cliquable, et cela n'a pas fonctionné parce que l'ordre setSpansétait appelé. Vous devez donc d'abord appeler le spannable.setSpan(clickableSpanand...puis le spannable.setSpan(new ForegroundColorSpan...pour obtenir la couleur dans le TextView

Tincho825
la source
4

J'ai fait cette petite fonction, il suffit de passer votre texte à la couleur, les index de début et de fin de ce que vous voulez colorer de ce texte et la couleur elle-même

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)
Gastón Saillén
la source
2

Avec une fonction d'extension Kotlin à usage général, cela ressemblerait à ceci:

/**
 * 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)
   }
}
Alejandro H. Cruz
la source
1

Utiliser des échappements de caractères + Html.fromHtml ()

entrez la description de l'image ici

Comment stocker la chaîne dans le dossier de ressources de chaîne

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

Comment afficher dans TextView?

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

Prime:

La chaîne dans la sortie ressemble à ceci

<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
la source
0

Inspiré de la réponse d'Alejandro H. Cruz ci-dessus .

Sa fonction ne fonctionne que pour une seule correspondance de sous-chaîne, j'ai mis à jour sa méthode pour utiliser Regex et je devrais mettre à jour les couleurs sur toutes les correspondances:

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)

    }
}
eppe
la source
-5

Une méthode consiste à en séparer myTextViewquelques-uns TextViews, dont l'un ne concerne que le code du téléphone. Ensuite, contrôler la couleur de ce spécifique TextViewest assez simple.

Ralkie
la source
7
Non, douleur dans le cul. Utiliser un spannable est la bonne façon.
Marc DiMillo
La classe Spannable peut le faire sans se séparer
Sz-Nika Janos