¿Cómo puedo cambiar el color de una parte de TextView?

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

Quiero cambiar el color de CepVizyon.getPhoneCode()la cadena de. ¿Cómo puedo hacer esto?

atasoyh
fuente
Posible duplicado del color establecido de TextView span en Android
Suragch
Esa pregunta se hizo el 19 de julio de 2010 a las 16:27, aproximadamente 3 meses antes que la suya. Sin embargo, no siempre es la publicación más antigua la que debe ser el destino duplicado. Se debe tener en cuenta el número de opiniones, el número de votos, el número de respuestas y la claridad de la pregunta. Al marcar esto como un duplicado, puede ayudar a las personas a encontrar esas otras respuestas que también responden a su pregunta.
Suragch
Consulte esto stackoverflow.com/a/57089362/6667442
Ketan Ramani
Para comprender realmente lo que hay detrás de escena, siempre sugiero leer un artículo detallado como este: medium.com/androiddevelopers/…
Michal Vician

Respuestas:

170

Spannable es más 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
fuente
3
¡Gracias por esta respuesta! Esto se parece más a NSAttributedString en iOS :) Para ser aún más flexible, reemplace text.lenght por text2.indexOf (CepVizyon.getPhoneCode ()) que le permite no conocer la primera parte de la cadena.
iGranDav
1
Debe poner ()después, text.lengthya que lengthes un método, no un campo. Lo haría yo mismo, pero las ediciones deben tener al menos 6 caracteres :)
MSX
Esta es la mejor respuesta con diferencia.
Pau Arlandis Martinez
1
El problema con Spannable es que ellipsize = end ya no funciona. Lo cual es un problema bastante serio en algunos casos.
Juan Carlos Ospina Gonzalez
1
Funciona muy bien. Aunque es aconsejable crear una cadena HTML. Y luego analizarlo a través de la clase 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
fuente
61

Si tiene texto estático que necesita color, puede agregarlo sin ningún código a través del archivo de cadenas:

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

luego

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

resultado

ingrese la descripción de la imagen aquí

No estoy seguro en qué versiones de API funciona, pero no funciona para la API 19 que he probado hasta ahora, por lo que probablemente solo algunas de las versiones de API más recientes admiten esto.

editar: como @hairraisin mencionó en los comentarios, intente usar en fgcolorlugar del colorcolor de la fuente, entonces debería funcionar para niveles de API más bajos, pero necesita más pruebas para estar seguro

Fonix
fuente
3
Probé con éxito usando <font fgcolor=...API 15 y API 25 (aunque no probé específicamente 19)
hair pasas
No funciona cuando configuro el texto mediante programación. :(
Rohit Singh
Ésta no es una solución ideal, porque mezcla traducciones con colores de texto.
Miloš Černilovský
16

Con respecto a la respuesta de Maneesh, esto funcionará, pero debe agregar y escapar las comillas para el atributo de color.

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

¡Es bueno para mi!

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

Aquí la solución en Kotlin que se usa SpannableStringpara cambiar el color de parte de una cuerda.

    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
fuente
1
Gracias. Solución simplemente elegante para Kotlin.
Nhon Nguyen
5

Aquí hay una colorizefunción basada en la respuesta 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
fuente
4

No me gustó la idea de hacer esto por código cada vez que quiero colorear partes del texto que he estado haciendo mucho en todas mis aplicaciones (y dado que en algunos casos el texto se está configurando en tiempo de ejecución con diferentes líneas en línea) colores definidos) así que creé el mío MarkableTextView.

La idea era:

  • Detectar etiquetas XML de cadenas
  • Identificar y hacer coincidir el nombre de la etiqueta
  • Extraer y guardar atributos y posición del texto.
  • Eliminar la etiqueta y conservar el contenido
  • Iterar atributos y aplicar estilos

Aquí está el proceso paso a paso:

Primero necesitaba una forma de encontrar etiquetas XML en una cadena determinada e Regexhice el truco.

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

Para que lo anterior coincida con una etiqueta XML, debe tener los siguientes criterios:

  • Nombre de etiqueta válido como <a> <a > <a-a> <a ..attrs..>pero no< a> <1>
  • Etiqueta de cierre que tiene un nombre coincidente como <a></a>pero no<a></b>
  • Cualquier contenido, ya que no es necesario diseñar "nada"

Ahora, para los atributos, usaremos este ...

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

Tiene el mismo concepto y, en general, no tuve que ir muy lejos para ambos, ya que el compilador se encargará del resto si algo sale de formato.

Ahora necesitamos una clase que pueda contener los datos 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 que nada, vamos a agregar este interesante iterador que he estado usando durante mucho tiempo para recorrer las coincidencias (no recuerdo el 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, aquí hay un modelador muy simple que hice para esta respuesta:

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

Y así Markablees como se ve la clase que contiene las definiciones:

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

Todo lo que necesitamos ahora es hacer referencia a una cadena y básicamente debería verse así:

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

Asegúrese de envolver las etiquetas con un CDATA Sectiony escapar "con \.

Hice esto como una solución modular para procesar partes del texto de diferentes maneras sin la necesidad de rellenar código innecesario detrás.

Explisam
fuente
4

Hice lo que dijo Andy Boot, pero también tenía un intervalo en el que se podía hacer clic, y no funcionó porque setSpansse llamó a la orden . Por lo tanto, primero debe llamar al y spannable.setSpan(clickableSpanand...luego al spannable.setSpan(new ForegroundColorSpan...para obtener el color en TextView

Tincho825
fuente
4

He hecho esta pequeña función, solo pasa tu texto a color, los índices de inicio y final de lo que quieres colorear de ese texto y el color en sí

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

Con una función de extensión de Kotlin de propósito general, se vería así:

/**
 * 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
fuente
1

Utilice caracteres de escape + Html.fromHtml ()

ingrese la descripción de la imagen aquí

Cómo almacenar la cadena en la carpeta de recursos de cadena

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

¿Cómo mostrar en TextView?

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

Prima:

La cadena en la salida se ve así

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

Una forma es dividirlos myTextViewen unos pocos TextViews, uno de los cuales sería solo para el código del teléfono. Entonces, controlar el color de este específico TextViewes bastante sencillo.

Ralkie
fuente
7
No, dolor en el trasero. Usar un spannable es la forma correcta.
Marc DiMillo
La clase Spannable puede hacer eso sin dividirse
Sz-Nika Janos