Recursos de cadenas (vistas)

Conceptos y la implementación de Jetpack Compose

Un recurso de cadenas proporciona cadenas de texto para tu aplicación con formato y estilo de texto opcionales. Existen tres tipos de recursos que pueden proporcionar strings para tu aplicación:

String
Recurso XML que proporciona una sola cadena.
Array de cadenas
Recurso XML que proporciona un array de cadenas.
Strings de cantidad (plurales)
Recurso XML que conlleva diferentes strings para brindar pluralización.

Todas las cadenas pueden aplicar algunos argumentos de formato y marcado de estilo. Para obtener información sobre las cadenas de formato y estilo, consulta la sección sobre Formato y estilo.

String

Una sola cadena a la que se puede hacer referencia desde la aplicación o desde otros archivos de recursos (como un diseño XML).

ubicación del archivo:
res/values/filename.xml
El nombre del archivo es arbitrario. El name del elemento de <string> se usa como ID del recurso.
tipo de datos de recursos compilados:
Puntero de recursos a un String.
referencia del recurso:
En Java: R.string.string_name
En XML: @string/string_name
sintaxis:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string
        name="string_name"
        >text_string</string>
</resources>
elementos:
<resources>
Obligatorio. Este debe ser el nodo raíz.

Sin atributos.

<string>
Cadena que puede incluir etiquetas de estilo. Ten en cuenta que se deben escapar los apóstrofos y las comillas. Para obtener más información sobre la manera de dar estilo y formato adecuadamente a tus strings, consulta Formato y estilo a continuación.

atributos:

name
String. Nombre para la cadena. El nombre se usa como ID del recurso.
ejemplo:
Archivo en formato XML guardado en res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello!</string>
</resources>

Este XML de diseño aplica una string a un objeto View:

<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello" />

Este código de la aplicación recupera una cadena:

Kotlin

val string: String = getString(R.string.hello)

Java

String string = getString(R.string.hello);

Puedes usar getString(int) o getText(int) para obtener una cadena. getText(int) retiene todo estilo de texto enriquecido aplicado a la cadena.

Array de cadenas

Array de cadenas al cual se puede hacer referencia desde la aplicación.

ubicación del archivo:
res/values/filename.xml
El nombre del archivo es arbitrario. El name del elemento de <string-array> se usa como ID del recurso.
tipo de datos de recursos compilados:
Puntero de recurso para un elemento de array de Strings.
referencia del recurso:
En Java: R.array.string_array_name
En XML: @[package:]array/string_array_name
sintaxis:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array
        name="string_array_name">
        <item
            >text_string</item>
    </string-array>
</resources>
elementos:
<resources>
Obligatorio. Este debe ser el nodo raíz.

Sin atributos.

<string-array>
Define un array de strings. Contiene uno o más elementos <item>.

atributos:

name
String. Es un nombre para el array. El nombre se usa como ID del recurso para hacer referencia al array.
<item>
Cadena que puede incluir etiquetas de estilo. El valor puede ser una referencia a otro recurso de strings. Tiene que ser un elemento secundario de un elemento <string-array>. Ten en cuenta que se deben escapar los apóstrofos y las comillas. Consulta Formato y estilo a continuación para obtener información sobre cómo dar formato y estilo adecuadamente a tus cadenas.

Sin atributos.

ejemplo:
Archivo en formato XML guardado en res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="planets_array">
        <item>Mercury</item>
        <item>Venus</item>
        <item>Earth</item>
        <item>Mars</item>
    </string-array>
</resources>
Este código de la aplicación recupera un array de cadenas:

Kotlin

val array: Array<String> = resources.getStringArray(R.array.planets_array)

Java

Resources res = getResources();
String[] planets = res.getStringArray(R.array.planets_array);

Strings de cantidad (plurales)

Los diferentes idiomas tienen distintas reglas de concordancia gramatical con la cantidad. En inglés, por ejemplo, la cantidad 1 es un caso especial. Escribimos "1 libro", pero, para las demás cantidades, se debe escribir "n libros". Esta distinción entre singular y plural es muy común; sin embargo, en otros idiomas, se hacen distinciones más detalladas. El conjunto completo compatible con Android es zero, one, two, few, many y other.

Las reglas para decidir el caso que se usará para un idioma y una cantidad determinados pueden ser muy complejas, por lo cual Android te proporciona métodos como getQuantityString() para seleccionar el recurso adecuado para ti.

En el nivel de API 24 y versiones posteriores, puedes usar la clase MessageFormat de ICU mucho más potente en su lugar.

ubicación del archivo:
res/values/filename.xml
El nombre del archivo es arbitrario. El name del elemento de <plurals> se usa como ID del recurso.
referencia del recurso:
En Java: R.plurals.plural_name
sintaxis:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals
        name="plural_name">
        <item
            quantity=["zero" | "one" | "two" | "few" | "many" | "other"]
            >text_string</item>
    </plurals>
</resources>
elementos:
<resources>
Obligatorio. Este debe ser el nodo raíz.

Sin atributos.

<plurals>
Una colección de cadenas, de las cuales se proporciona una cadena según la cantidad de algún elemento. Contiene uno o más elementos <item>.

atributos:

name
String. Nombre para el par de cadenas. El nombre se usa como ID del recurso.
</dd>

<item>
Una cadena singular o plural. El valor puede ser una referencia a otro recurso de strings. Tiene que ser un elemento secundario de un elemento <plurals>. Ten en cuenta que se deben escapar los apóstrofos y las comillas. Consulta Formato y estilo a continuación para obtener información sobre cómo dar formato y estilo adecuadamente a tus cadenas.

atributos:

quantity
Palabra clave. Valor que indica cuándo debe usarse esta cadena. Valores válidos, con ejemplos no exhaustivos entre paréntesis:
ValorDescripción
zeroCuando el idioma requiere tratamiento especial del número 0 (como en árabe).
oneCuando el idioma requiere tratamiento especial de números como el uno (como el número 1 en inglés y la mayoría de los demás idiomas; en ruso, se encuentran dentro de esta clase todos los números que finalizan en 1, pero no en 11).
twoCuando el idioma requiere tratamiento especial de números como el dos (como el 2 en galés, o el 102 en esloveno).
fewCuando el idioma requiere tratamiento especial de números "pequeños" (como el 2, el 3 y el 4 en checo, o los números que finalizan en 2, 3 o 4, excepto el 12, el 13 o el 14 en polaco).
manyCuando el idioma requiere tratamiento especial de números "grandes" (como los números que finalizan en 11-99 en maltés).
otherCuando el idioma no requiere tratamiento especial de la cantidad especificada (como todos los números en chino o el 42 en inglés).

ejemplo:
Archivo en formato XML guardado en res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals name="numberOfSongsAvailable">
        <!--
             As a developer, you should always supply "one" and "other"
             strings. Your translators will know which strings are actually
             needed for their language. Always include %d in "one" because
             translators will need to use %d for languages where "one"
             doesn't mean 1 (as explained above).
          -->
        <item quantity="one">%d song found.</item>
        <item quantity="other">%d songs found.</item>
    </plurals>
</resources>

Archivo en formato XML guardado en res/values-pl/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals name="numberOfSongsAvailable">
        <item quantity="one">Znaleziono %d piosenkę.</item>
        <item quantity="few">Znaleziono %d piosenki.</item>
        <item quantity="other">Znaleziono %d piosenek.</item>
    </plurals>
</resources>

Uso:

Kotlin

val count = getNumberOfSongsAvailable()
val songsFound = resources.getQuantityString(R.plurals.numberOfSongsAvailable, count, count)

Java

int count = getNumberOfSongsAvailable();
Resources res = getResources();
String songsFound = res.getQuantityString(R.plurals.numberOfSongsAvailable, count, count);

Cuando se usa el método getQuantityString(), debes pasar count dos veces si tu cadena incluye formato de cadena. Por ejemplo, para la cadena %d songs found, el primer parámetro de count selecciona la cadena plural adecuada y el segundo parámetro de count se inserta en el marcador de posición %d. Si tus cadenas de plural no incluyen formato de cadena, no es necesario que pases el tercer parámetro a getQuantityString.

Formato y estilo

A continuación, se mencionan algunos puntos importantes que debes conocer sobre cómo dar formato y estilo adecuadamente a tus recursos de cadenas.

Cómo dar formato a las strings

Si necesitas dar formato a tus cadenas, puedes hacerlo disponiendo tus argumentos de formato en el recurso de cadenas, tal como se demuestra en el siguiente recurso de ejemplo.

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

En este ejemplo, la cadena de formato tiene dos argumentos: %1$s es una cadena y %2$d es un número decimal. Luego, dale formato a la cadena llamando a getString(int, Object...). Por ejemplo:

Kotlin

var text = getString(R.string.welcome_messages, username, mailCount)

Java

String text = getString(R.string.welcome_messages, username, mailCount);

Cómo dar estilo con el lenguaje de marcado HTML

Puedes agregar estilo a tus strings con el lenguaje de marcado HTML. Por ejemplo:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="welcome">Welcome to <b>Android</b>!</string>
</resources>

Si no aplicas formato, puedes establecer el texto de TextView directamente llamando a setText(java.lang.CharSequence). Sin embargo, en algunos casos, es posible que desees crear un recurso de texto con estilo que también se use como cadena de formato. Generalmente, esto no funciona porque los métodos format(String, Object...) y getString(int, Object...) quitan toda la información de estilo de la cadena. La solución temporal para esto es escribir las etiquetas HTML con entidades escapadas, que se recuperan después con fromHtml(String) una vez que se lleva a cabo el formato. Por ejemplo:

  1. Almacena tu recurso de texto con estilo como una string escapada con HTML:
    <resources>
      <string name="welcome_messages">Hello, %1$s! You have &lt;b>%2$d new messages&lt;/b>.</string>
    </resources>

    En esta string con formato, se agrega un elemento <b>. Ten en cuenta que el corchete de apertura está con escape HTML mediante la anotación &lt;.

  2. Luego, da formato a la string como siempre, pero también llama a fromHtml(String) para convertir el texto HTML en texto con estilo:

    Kotlin

    val text: String = getString(R.string.welcome_messages, username, mailCount)
    val styledText: Spanned = Html.fromHtml(text, FROM_HTML_MODE_LEGACY)

    Java

    String text = getString(R.string.welcome_messages, username, mailCount);
    Spanned styledText = Html.fromHtml(text, FROM_HTML_MODE_LEGACY);

Debido a que el método fromHtml(String) da formato a todas las entidades HTML, asegúrate de escapar todos los caracteres HTML posibles en las cadenas que usas con el texto con formato, mediante htmlEncode(String). Por ejemplo, si das formato a una cadena que contiene caracteres como "<" o "&", se deben escapar antes de dar formato, de modo que, cuando se pase la cadena con formato por fromHtml(String), los caracteres aparezcan como se escribieron originalmente. Por ejemplo:

Kotlin

val escapedUsername: String = TextUtils.htmlEncode(username)

val text: String = getString(R.string.welcome_messages, escapedUsername, mailCount)
val styledText: Spanned = Html.fromHtml(text, FROM_HTML_MODE_LEGACY)

Java

String escapedUsername = TextUtils.htmlEncode(username);

String text = getString(R.string.welcome_messages, escapedUsername, mailCount);
Spanned styledText = Html.fromHtml(text);

Cómo dar estilo con Spannable

Un Spannable es un objeto de texto al que puedes dar estilo con propiedades de tipos de letras, como color y grosor de la fuente. Usas SpannableStringBuilder para crear tu texto y, luego, aplicar estilos definidos en el paquete de android.text.style al texto.

Puedes usar los siguientes métodos auxiliares para configurar gran parte del trabajo de crear texto de Spannable:

Kotlin

/**
 * Returns a CharSequence that concatenates the specified array of CharSequence
 * objects and then applies a list of zero or more tags to the entire range.
 *
 * @param content an array of character sequences to apply a style to
 * @param tags the styled span objects to apply to the content
 *        such as android.text.style.StyleSpan
 */
private fun apply(content: Array<out CharSequence>, vararg tags: Any): CharSequence {
    return SpannableStringBuilder().apply {
        openTags(tags)
        content.forEach { charSequence ->
            append(charSequence)
        }
        closeTags(tags)
    }
}

/**
 * Iterates over an array of tags and applies them to the beginning of the specified
 * Spannable object so that future text appended to the text will have the styling
 * applied to it. Do not call this method directly.
 */
private fun Spannable.openTags(tags: Array<out Any>) {
    tags.forEach { tag ->
        setSpan(tag, 0, 0, Spannable.SPAN_MARK_MARK)
    }
}

/**
 * "Closes" the specified tags on a Spannable by updating the spans to be
 * endpoint-exclusive so that future text appended to the end will not take
 * on the same styling. Do not call this method directly.
 */
private fun Spannable.closeTags(tags: Array<out Any>) {
    tags.forEach { tag ->
    if (length > 0) {
            setSpan(tag, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        } else {
            removeSpan(tag)
        }
    }
}

Java

/**
 * Returns a CharSequence that concatenates the specified array of CharSequence
 * objects and then applies a list of zero or more tags to the entire range.
 *
 * @param content an array of character sequences to apply a style to
 * @param tags the styled span objects to apply to the content
 *        such as android.text.style.StyleSpan
 *
 */
private static CharSequence applyStyles(CharSequence[] content, Object[] tags) {
    SpannableStringBuilder text = new SpannableStringBuilder();
    openTags(text, tags);
    for (CharSequence item : content) {
        text.append(item);
    }
    closeTags(text, tags);
    return text;
}

/**
 * Iterates over an array of tags and applies them to the beginning of the specified
 * Spannable object so that future text appended to the text will have the styling
 * applied to it. Do not call this method directly.
 */
private static void openTags(Spannable text, Object[] tags) {
    for (Object tag : tags) {
        text.setSpan(tag, 0, 0, Spannable.SPAN_MARK_MARK);
    }
}

/**
 * "Closes" the specified tags on a Spannable by updating the spans to be
 * endpoint-exclusive so that future text appended to the end will not take
 * on the same styling. Do not call this method directly.
 */
private static void closeTags(Spannable text, Object[] tags) {
    int len = text.length();
    for (Object tag : tags) {
        if (len > 0) {
            text.setSpan(tag, 0, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        } else {
            text.removeSpan(tag);
        }
    }
}

Los siguientes métodos bold, italic y color contienen los métodos de ayuda anteriores y permiten ver ejemplos específicos de cómo dar estilos definidos en el paquete android.text.style. Puedes crear métodos similares para dar otros tipos de estilos al texto.

Kotlin

/**
 * Returns a CharSequence that applies boldface to the concatenation
 * of the specified CharSequence objects.
 */
fun bold(vararg content: CharSequence): CharSequence = apply(content, StyleSpan(Typeface.BOLD))

/**
 * Returns a CharSequence that applies italics to the concatenation
 * of the specified CharSequence objects.
 */
fun italic(vararg content: CharSequence): CharSequence = apply(content, StyleSpan(Typeface.ITALIC))

/**
 * Returns a CharSequence that applies a foreground color to the
 * concatenation of the specified CharSequence objects.
 */
fun color(color: Int, vararg content: CharSequence): CharSequence =
        apply(content, ForegroundColorSpan(color))

Java

/**
 * Returns a CharSequence that applies boldface to the concatenation
 * of the specified CharSequence objects.
 */
public static CharSequence bold(CharSequence... content) {
    return apply(content, new StyleSpan(Typeface.BOLD));
}

/**
 * Returns a CharSequence that applies italics to the concatenation
 * of the specified CharSequence objects.
 */
public static CharSequence italic(CharSequence... content) {
    return apply(content, new StyleSpan(Typeface.ITALIC));
}

/**
 * Returns a CharSequence that applies a foreground color to the
 * concatenation of the specified CharSequence objects.
 */
public static CharSequence color(int color, CharSequence... content) {
    return apply(content, new ForegroundColorSpan(color));
}

Este es un ejemplo de cómo encadenar estos métodos para aplicar diversos estilos a palabras específicas en una frase:

Kotlin

// Create an italic "hello, " a red "world",
// and bold the entire sequence.
val text: CharSequence = bold(italic(getString(R.string.hello)),
        color(Color.RED, getString(R.string.world)))

Java

// Create an italic "hello, " a red "world",
// and bold the entire sequence.
CharSequence text = bold(italic(getString(R.string.hello)),
    color(Color.RED, getString(R.string.world)));

El módulo core-ktx de Kotlin también contiene funciones de extensión que facilitan mucho más el trabajo con intervalos. Para obtener más información, puedes consultar la documentación del paquete android.text en GitHub.

Para obtener más información sobre el trabajo con intervalos, consulta estos enlaces:

Cómo dar estilo con anotaciones

Puedes aplicar estilos complejos o personalizados si usas la clase Annotation junto con la etiqueta <annotation> en tus archivos de recursos strings.xml. La etiqueta de anotación permite marcar partes de la cadena para darles un estilo personalizado. Para hacerlo, deben definirse pares clave-valor personalizados en el XML que el framework convertirá en intervalos de Annotation. Después, puedes recuperar estas anotaciones y usar la clave y el valor para aplicar el diseño.

Cuando creas anotaciones, asegúrate de agregar la etiqueta <annotation> a todas las traducciones de la string en cada archivo strings.xml.


Aplicar un tipo de letra personalizado a la palabra "texto" en todos los idiomas

Ejemplo: Agregar un tipo de letra personalizado

  1. Agrega la etiqueta <annotation> y define el par clave-valor. En este caso, la clave es font y el valor es el tipo de fuente que queremos usar: title_emphasis.

    // values/strings.xml
    <string name="title">Best practices for <annotation font="title_emphasis">text</annotation> on Android</string>
    
    // values-es/strings.xml
    <string name="title"><annotation font="title_emphasis">Texto</annotation> en Android: mejores prácticas</string>
  2. Carga el recurso de strings y busca las anotaciones con la clave font. Después, crea un intervalo personalizado y reemplaza el existente.

    Kotlin

    // get the text as SpannedString so we can get the spans attached to the text
    val titleText = getText(R.string.title) as SpannedString
    
    // get all the annotation spans from the text
    val annotations = titleText.getSpans(0, titleText.length, Annotation::class.java)
    
    // create a copy of the title text as a SpannableString.
    // the constructor copies both the text and the spans. so we can add and remove spans
    val spannableString = SpannableString(titleText)
    
    // iterate through all the annotation spans
    for (annotation in annotations) {
       // look for the span with the key font
       if (annotation.key == "font") {
          val fontName = annotation.value
          // check the value associated to the annotation key
          if (fontName == "title_emphasis") {
             // create the typeface
             val typeface = getFontCompat(R.font.permanent_marker)
             // set the span at the same indices as the annotation
             spannableString.setSpan(CustomTypefaceSpan(typeface),
                titleText.getSpanStart(annotation),
                titleText.getSpanEnd(annotation),
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
          }
       }
    }
    
    // now, the spannableString contains both the annotation spans and the CustomTypefaceSpan
    styledText.text = spannableString

    Java

    // get the text as SpannedString so we can get the spans attached to the text
    SpannedString titleText = (SpannedString) getText(R.string.title);
    
    // get all the annotation spans from the text
    Annotation[] annotations = titleText.getSpans(0, titleText.length(), Annotation.class);
    
    // create a copy of the title text as a SpannableString.
    // the constructor copies both the text and the spans. so we can add and remove spans
    SpannableString spannableString = new SpannableString(titleText);
    
    // iterate through all the annotation spans
    for (Annotation annotation: annotations) {
      // look for the span with the key font
      if (annotation.getKey().equals("font")) {
        String fontName = annotation.getValue();
        // check the value associated to the annotation key
        if (fontName.equals("title_emphasis")) {
        // create the typeface
        Typeface typeface = ResourcesCompat.getFont(this, R.font.roboto_mono);
        // set the span at the same indices as the annotation
        spannableString.setSpan(new CustomTypefaceSpan(typeface),
          titleText.getSpanStart(annotation),
          titleText.getSpanEnd(annotation),
          Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
      }
    }
    
    // now, the spannableString contains both the annotation spans and the CustomTypefaceSpan
    styledText.text = spannableString;

Si usas el mismo texto varias veces, debes construir el objeto SpannableString una vez y volver a usarlo cuando lo necesites. Así evitarás posibles problemas de rendimiento y memoria.

Para conocer otros ejemplos del uso de anotaciones, consulta Cómo dar estilo a texto internacionalizado en Android.

Intervalos de anotación y parcelas de texto

Dado que los intervalos de Annotation también son ParcelableSpans, los pares clave-valor pueden dividirse y reunirse en parcelas. Siempre y cuando el receptor de la parcela sepa cómo interpretar las anotaciones, puedes usar intervalos de Annotation para aplicar estilos personalizados al texto en parcelas.

Para mantener el diseño personalizado cuando pasas el texto a un Intent Bundle, primero debes agregar intervalos de Annotation a tu texto. Puedes hacerlo en los recursos XML usando la etiqueta <annotation>, como se muestra en el ejemplo anterior, o en el código, creando una nueva Annotation y definiéndola como intervalo, tal como se ejemplifica a continuación:

Kotlin

val spannableString = SpannableString("My spantastic text")
val annotation = Annotation("font", "title_emphasis")
spannableString.setSpan(annotation, 3, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

// start Activity with text with spans
val intent = Intent(this, MainActivity::class.java)
intent.putExtra(TEXT_EXTRA, spannableString)
startActivity(intent)

Java

SpannableString spannableString = new SpannableString("My spantastic text");
Annotation annotation = new Annotation("font", "title_emphasis");
spannableString.setSpan(annotation, 3, 7, 33);

// start Activity with text with spans
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra(TEXT_EXTRA, spannableString);
this.startActivity(intent);

Recupera el texto del Bundle como un SpannableString y, luego, analiza las anotaciones adjuntas, como se muestra en el ejemplo anterior.

Kotlin

// read text with Spans
val intentCharSequence = intent.getCharSequenceExtra(TEXT_EXTRA) as SpannableString

Java

// read text with Spans
SpannableString intentCharSequence = (SpannableString)intent.getCharSequenceExtra(TEXT_EXTRA);

Para obtener más información sobre cómo dar estilo al texto, consulta los siguientes enlaces: