Texto en Compose

El texto es una pieza central de cualquier IU, y Jetpack Compose facilita su visualización o escritura. Compose aprovecha la composición de sus bloques de compilación, lo que significa que no es necesario anular propiedades ni métodos, ni extender grandes clases para tener una lógica y un diseño componible específicos que funcionen como tú quieres.

En su nivel más básico, Compose proporciona un BasicText y un BasicTextField, que es lo básico para mostrar texto y controlar entradas del usuario. En un nivel superior, Compose proporciona Text y TextField, que son elementos componibles que siguen los lineamientos de Material Design. Se recomienda usarlos, ya que tienen el aspecto correcto para los usuarios de Android. También incluye otras opciones que simplifican su personalización sin tener que escribir mucho código.

Cómo mostrar texto

La forma más básica de mostrar texto es usar el elemento componible Text con una String como argumento:

@Composable
fun SimpleText() {
  Text("Hello World")
}

La frase "Hello World" en texto negro sin formato

Cómo mostrar texto desde un recurso

Te recomendamos que uses recursos de string en lugar de codificar valores Text, ya que puedes compartir las mismas strings con Views de Android y preparar tu app para la internacionalización:

@Composable
fun StringResourceText() {
  Text(stringResource(R.string.hello_world))
}

Cómo aplicar estilo al texto

El elemento componible Text tiene varios parámetros opcionales para aplicarle estilo a su contenido. A continuación, incluimos algunos parámetros que abarcan los casos de uso más comunes con texto. Para ver todos los parámetros de Text, te recomendamos que revises el código fuente de texto de Compose.

Cada vez que configuras uno de estos parámetros, aplicas el estilo a todo el valor de texto. Si necesitas aplicar varios estilos en la misma línea o párrafos, consulta la sección sobre varios estilos intercalados.

Cómo cambiar el color del texto

@Composable
fun BlueText() {
  Text("Hello World", color = Color.Blue)
}

La frase "Hello World" en texto azul

Cómo cambiar el tamaño del texto

@Composable
fun BigText() {
  Text("Hello World", fontSize = 30.sp)
}

La frase "Hello World" en un tamaño de fuente más grande

Cómo convertir texto en cursiva

@Composable
fun ItalicText() {
  Text("Hello World", fontStyle = FontStyle.Italic)
}

La frase "Hello World" en cursiva

Cómo aplicar negrita al texto

@Composable
fun BoldText() {
    Text("Hello World", fontWeight = FontWeight.Bold)
}

La frase "Hello World" en negrita

Alineaciones de texto

El parámetro textAlign permite establecer la alineación del texto dentro de un área de superficie componible Text.

De forma predeterminada, Text seleccionará la alineación del texto natural según su valor de contenido:

  • Borde izquierdo del contenedor Text para alfabetos de izquierda a derecha, como el latino, el cirílico o el hangul
  • Borde derecho del contenedor Text para alfabetos de derecha a izquierda, como el árabe o el hebreo
@Preview(showBackground = true)
@Composable
fun CenterText() {
    Text("Hello World", textAlign = TextAlign.Center,
                modifier = Modifier.width(150.dp))
}

La frase "Hello World" centrada en su elemento contenedor

Si quieres configurar de forma manual la alineación del texto de un elemento componible Text, usa TextAlign.Start y TextAlign.End en lugar de TextAlign.Left y TextAlign.Right, respectivamente, ya que se resuelven en el borde derecho de Text, según la orientación de texto del idioma preferido. Por ejemplo, TextAlign.End se alinea con el lado derecho para el texto en francés y el lado izquierdo para el texto en árabe, pero TextAlign.Right se alinea con el lado derecho, sin importar qué opción se use.

Cómo trabajar con fuentes

Text tiene un parámetro fontFamily para permitir la configuración de la fuente usada en el elemento componible. Según la configuración predeterminada, se incluyen las familias de fuentes serif, Sans Serif, polémicas, monospaciales y cursivas:

@Composable
fun DifferentFonts() {
    Column {
        Text("Hello World", fontFamily = FontFamily.Serif)
        Text("Hello World", fontFamily = FontFamily.SansSerif)
    }
}

La frase "Hello World" en dos fuentes diferentes, con y sin serif

Puedes usar el atributo fontFamily para trabajar con fuentes personalizadas y tipos de letra definidos en la carpeta res/fonts:

Representación gráfica de la carpeta res > font en el entorno de desarrollo

En este ejemplo, se muestra cómo definirías una fontFamily según esos archivos de fuente:

val firaSansFamily = FontFamily(
        Font(R.font.firasans_light, FontWeight.Light),
        Font(R.font.firasans_regular, FontWeight.Normal),
        Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
        Font(R.font.firasans_medium, FontWeight.Medium),
        Font(R.font.firasans_bold, FontWeight.Bold)
)

Por último, puedes pasar esta fontFamily a tu objeto Text componible. Debido a que una fontFamily puede incluir diferentes pesos, puedes configurar manualmente fontWeight a fin de seleccionar el peso correcto para tu texto:

Column {
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
    Text(
        ..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal,
        fontStyle = FontStyle.Italic
    )
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
}

La frase "Hello World" en varios pesos y estilos diferentes

Para descubrir cómo establecer la tipografía en toda tu app, consulta la documentación sobre temas.

Varios estilos en un texto

Para configurar estilos diferentes en el mismo elemento componible Text, debes usar una AnnotatedString, una string que se puede anotar con estilos de anotaciones arbitrarias.

AnnotatedString es una clase de datos que contiene:

  • Un valor Text
  • Una List de SpanStyleRange, equivalente al estilo intercalado con el rango de posición dentro del valor de texto
  • Una List de ParagraphStyleRange que especifica la alineación del texto, la dirección del texto, la altura de la línea y el estilo de sangría del texto

TextStyle es para uso en el elemento componible Text, mientras que SpanStyle y ParagraphStyle se usan en AnnotatedString.

La diferencia entre SpanStyle y ParagraphStyle es que ParagraphStyle se puede aplicar a un párrafo completo, mientras que SpanStyle se puede aplicar a nivel de carácter. Una vez que una parte del texto se marca con un ParagraphStyle, esa parte queda separada del resto como si tuviera feeds de líneas al principio y al final.

AnnotatedString tiene un compilador de tipo seguro para facilitar la creación:

@Composable
fun MultipleStylesInText() {
    Text(
        buildAnnotatedString {
            withStyle(style = SpanStyle(color = Color.Blue)) {
                append("H")
            }
            append("ello ")

            withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
                append("W")
            }
            append("orld")
        }
    )
}

La frase "Hello World" con varios cambios de estilo intercalados; la H es azul, y la W es roja y está en negrita

Podemos establecer los estilos de párrafo de la misma manera:

@Composable
fun ParagraphStyle() {
    Text(
        buildAnnotatedString {
            withStyle(style = ParagraphStyle(lineHeight = 30.sp)) {
                withStyle(style = SpanStyle(color = Color.Blue)) {
                    append("Hello\n")
                }
                withStyle(
                    style = SpanStyle(
                        fontWeight = FontWeight.Bold,
                        color = Color.Red
                    )
                ) {
                    append("World\n")
                }
                append("Compose")
            }
        }
    )
}

Tres párrafos en tres estilos diferentes: azul, rojo y en negrita, y negro simple

Cantidad máxima de líneas

Para limitar la cantidad de líneas visibles en un elemento componible Text, establece el parámetro maxLines:

@Composable
fun LongText() {
    Text("hello ".repeat(50), maxLines = 2)
}

Un pasaje de texto largo truncado después de dos líneas

Desbordamiento de texto

A la hora de limitar un texto largo, es posible que desees indicar un desbordamiento de texto, que solo se muestra si el texto está truncado. Para hacerlo, configura el parámetro textOverflow:

@Composable
fun OverflowedText() {
    Text("Hello Compose ".repeat(50), maxLines = 2, overflow = TextOverflow.Ellipsis)
}

Un largo pasaje de texto truncado después de tres líneas, con una elipsis al final

Temas

Para usar un tema en una app a fin de diseñar estilos de texto, consulta la documentación sobre temas.

Interacciones del usuario

Jetpack Compose habilita la interactividad detallada en Text. La selección de texto ahora es más flexible y se puede realizar en diseños componibles. Las interacciones de los usuarios en el texto son diferentes de otros diseños componibles, ya que no puedes agregar un modificador a una parte de un elemento Text componible. En esta sección, se destacan las diferentes API para permitir interacciones de usuario.

Cómo seleccionar texto

De forma predeterminada, no se pueden seleccionar elementos componibles, lo que significa que, de forma predeterminada, los usuarios no pueden seleccionar y copiar texto desde tu app. Para habilitar la selección de texto, debes unir tus elementos de texto con un elemento componible SelectionContainer:

@Composable
fun SelectableText() {
    SelectionContainer {
        Text("This text is selectable")
    }
}

Un pasaje de texto breve seleccionado por el usuario

Puede que quieras inhabilitar la selección en partes específicas de un área seleccionable. Para hacerlo, debes unir la parte no seleccionable con un elemento componible DisableSelection:

@Composable
fun PartiallySelectableText() {
    SelectionContainer {
        Column {
            Text("This text is selectable")
            Text("This one too")
            Text("This one as well")
            DisableSelection {
                Text("But not this one")
                Text("Neither this one")
            }
            Text("But again, you can select this one")
            Text("And this one too")
        }
    }
}

Un pasaje de texto más largo. El usuario intentó seleccionar todo el fragmento, pero no se resaltaron las dos líneas porque se inhabilitó DisableSelection.

Cómo obtener la posición de un clic en el texto

Para escuchar los clics en Text, puedes agregar el modificador clickable. Sin embargo, si quieres obtener la posición de un clic dentro de un Text componible (suponiendo que tienes acciones distintas para diferentes partes del texto), debes usar un ClickableText:

@Composable
fun SimpleClickableText() {
    ClickableText(
        text = AnnotatedString("Click Me"),
        onClick = { offset ->
            Log.d("ClickableText", "$offset -th character is clicked.")
        }
    )
}

Cómo hacer clic con la anotación

Si un usuario hace clic en un elemento componible Text, tal vez quieras adjuntar información adicional en una parte del valor Text, como una URL adjunta a una palabra específica para abrirla en un navegador. Para hacerlo, debes adjuntar una anotación, que toma una etiqueta (String), un elemento (String) y un rango de texto como parámetros. Desde una AnnotatedString, estas anotaciones se pueden filtrar con sus etiquetas o rangos de texto. Veamos un ejemplo:

@Composable
fun AnnotatedClickableText() {
    val annotatedText = buildAnnotatedString {
        append("Click ")

        // We attach this *URL* annotation to the following content
        // until `pop()` is called
        pushStringAnnotation(tag = "URL",
                             annotation = "https://developer.android.com")
        withStyle(style = SpanStyle(color = Color.Blue,
                                    fontWeight = FontWeight.Bold)) {
            append("here")
        }

        pop()
    }

    ClickableText(
        text = annotatedText,
        onClick = { offset ->
            // We check if there is an *URL* annotation attached to the text
            // at the clicked position
            annotatedText.getStringAnnotations(tag = "URL", start = offset,
                                                    end = offset)
                .firstOrNull()?.let { annotation ->
                    // If yes, we log its value
                    Log.d("Clicked URL", annotation.item)
                }
        }
    )
}

Cómo ingresar y modificar texto

TextField permite a los usuarios ingresar y modificar texto. Hay dos niveles de implementaciones TextField:

  1. TextField es la implementación de Material Design. Te recomendamos que elijas esta implementación, ya que sigue los lineamientos de material design:
    • El estilo predeterminado está relleno.
    • OutlinedTextField es la versión de estilo de contorno.
  2. BasicTextField permite a los usuarios editar texto mediante el teclado en pantalla o físico, pero no proporciona decoraciones como sugerencias o marcadores de posición.
@Composable
fun SimpleFilledTextFieldSample() {
    var text by remember { mutableStateOf("Hello") }

    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

Un campo de texto editable que contiene la palabra "Hola". El campo tiene la etiqueta no editable "label".

@Composable
fun SimpleOutlinedTextFieldSample() {
    var text by remember { mutableStateOf("") }

    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

Campo de texto editable, con la etiqueta y un borde púrpura.

Cómo aplicar estilo a TextField

TextField y BasicTextField comparten muchos parámetros comunes de personalización. La lista completa para TextField está disponible en el código fuente de TextField. Esta es una lista no exhaustiva de algunos de los parámetros útiles:

  • singleLine
  • maxLines
  • textStyle
@Composable
fun StyledTextField() {
    var value by remember { mutableStateOf("Hello\nWorld\nInvisible") }

    TextField(
        value = value,
        onValueChange = { value = it },
        label = { Text("Enter text") },
        maxLines = 2,
        textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
        modifier = Modifier.padding(20.dp)
    )
}

Un TextField de varias líneas, con dos líneas editables más la etiqueta

Recomendamos usar TextField en lugar de BasicTextField cuando el diseño llame a un campo TextField o OutlineTextField de Material. Sin embargo, BasicTextField debe usarse cuando se crean diseños que no necesitan las decoraciones de la especificación de Material.

Opciones del teclado

TextField te permite establecer opciones de configuración del teclado, como el diseño, o bien habilitar la autocorrección si es compatible con el teclado. Es posible que algunas opciones no estén garantizadas si el teclado en pantalla no cumple con las opciones que se proporcionan aquí. Esta es la lista de las opciones de teclado compatibles:

  • capitalization
  • autoCorrect
  • keyboardType
  • imeAction

Cómo agregar formato

TextField te permite configurar un formato visual para el valor de entrada, como reemplazar caracteres por * para contraseñas o insertar guiones cada 4 dígitos para un número de tarjeta de crédito:

@Composable
fun PasswordTextField() {
    var password by rememberSaveable { mutableStateOf("") }

    TextField(
        value = password,
        onValueChange = { password = it },
        label = { Text("Enter password") },
        visualTransformation = PasswordVisualTransformation(),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
    )
}

Un campo de entrada de texto con contraseña, con el texto enmascarado

Puedes encontrar más ejemplos en el código fuente de VisualTransformSamples.