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 componentes básicos, 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 componibles específicos que funcionen como tú quieres.
En su nivel más básico, Compose brinda un BasicText
y un BasicTextField
, que son elementos básicos para mostrar texto y controlar entradas del usuario. En un nivel superior, Compose brinda Text
y TextField
, que son elementos que admiten composición y siguen los lineamientos de Material Design. Se recomienda su uso, ya que tienen el aspecto correcto para los usuarios de Android. También incluyen 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")
}
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)
}
Cómo cambiar el tamaño del texto
@Composable
fun BigText() {
Text("Hello World", fontSize = 30.sp)
}
Cómo convertir texto en cursiva
Usa el parámetro fontStyle
para que el texto aparezca en cursiva (o configurar otro FontStyle
).
@Composable
fun ItalicText() {
Text("Hello World", fontStyle = FontStyle.Italic)
}
Cómo aplicar negrita al texto
Usa el parámetro fontWeight
para que el texto aparezca en negrita (o configura otro FontWeight
).
@Composable
fun BoldText() {
Text("Hello World", fontWeight = FontWeight.Bold)
}
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))
}
Si quieres configurar manualmente 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 con el lado izquierdo para el texto en árabe, pero TextAlign.Right
se alinea con el lado derecho, sin importar qué alfabeto se use.
Sombra
El parámetro style
permite establecer un objeto de tipo TextStyle
y configurar varios parámetros, como la sombra.
Shadow
recibe un color para la sombra, el desplazamiento o su ubicación con respecto al Text
y el radio de desenfoque, que es el aspecto borroso.
@Preview(showBackground = true)
@Composable
fun TextShadow() {
val offset = Offset(5.0f, 10.0f)
Text(
text = "Hello world!",
style = TextStyle(
fontSize = 24.sp,
shadow = Shadow(
color = Color.Blue,
offset = offset,
blurRadius = 3f
)
)
)
}
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, monoespaciales y cursivas:
@Composable
fun DifferentFonts() {
Column {
Text("Hello World", fontFamily = FontFamily.Serif)
Text("Hello World", fontFamily = FontFamily.SansSerif)
}
}
Puedes usar el atributo fontFamily
para trabajar con fuentes personalizadas y tipos de letra definidos en la carpeta res/font
:
font en el entorno de desarrollo " class="l10n-absolute-url-src screenshot" l10n-attrs-original-order="src,alt,width,class" src="https://developer.android.com/static/images/jetpack/compose/text-font-folder.png" width="400" />
En este ejemplo, se muestra el modo en que definirás una fontFamily
según esos archivos de fuente con el uso de la función Font
:
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)
}
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 que admite composición Text
, debes usar una AnnotatedString
, una string que se puede anotar con estilos de anotaciones arbitrarias.
AnnotatedString
es una clase de datos que contiene lo siguiente:
- Un valor
Text
- Una
List
deSpanStyleRange
, equivalente al estilo intercalado con el rango de posición dentro del valor de texto - Una
List
deParagraphStyleRange
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
puede aplicarse 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: buildAnnotatedString
.
@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")
}
)
}
Podemos configurar ParagraphStyle
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")
}
}
)
}
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)
}
Desbordamiento de texto
A la hora de limitar un texto largo, es posible que desees indicar un TextOverflow
, 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)
}
APIs de includeFontPadding y lineHeight
includeFontPadding
es una propiedad heredada que agrega padding adicional en función de las métricas de fuente en la parte superior de la primera línea y en la parte inferior de la última línea de un texto.
En Compose 1.2.0, includeFontPadding se configura como verdadero de forma predeterminada.
Ahora recomendamos configurar includeFontPadding
como falso (lo que quitará el padding adicional) mediante la API obsoleta de PlatformTextStyle
de Compose 1.2.0, y ajusta el texto aún más.
La capacidad de configurar lineHeight
no es nueva. Está disponible desde Android Q. Puedes configurar lineHeight
para Text
mediante el parámetro lineHeight
, que distribuye la altura de la línea en cada línea de texto. Luego, puedes usar el nuevo LineHeightStyle API
para configurar aún más cómo se alinea este texto dentro del espacio y quitar los espacios en blanco.
Es posible que desees ajustar lineHeight
mediante la unidad de texto "em" (tamaño de fuente relativo) en lugar de "sp" (píxeles ajustados) para mejorar la precisión. Aquí se documenta más información sobre cómo seleccionar una unidad de texto correcta.

@Composable
fun AlignedText() {
Text(
text = myText,
style = LocalTextStyle.current.merge(
TextStyle(
lineHeight = 2.5.em,
platformStyle = PlatformTextStyle(
includeFontPadding = false
),
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None
)
)
)
)
}
Además de ajustar lineHeight
, ahora puedes centrar más el texto y darle más estilo mediante los parámetros de configuración con la API experimental de LineHeightStyle
: LineHeightStyle.Alignment
y LineHeightStyle.Trim
(includeFontPadding
debe estar configurado como falso para que Trim funcione). Alignment y Trim utilizan el espacio medido entre las líneas de texto para distribuirlo de manera más adecuada en todas las líneas, lo que incluye una sola línea de texto y la línea superior de un bloque de texto.
LineHeightStyle.Alignment
define cómo alinear la línea en el espacio que proporciona la altura de la línea. Dentro de cada una, puedes alinear el texto en la parte superior, inferior, central o de forma proporcional. Luego, LineHeightStyle.Trim
te permite dejar o quitar el espacio adicional en la parte superior de la primera línea y en la parte inferior de la última línea del texto, que se genera de cualquier ajuste de lineHeight
y Alignment. En los siguientes ejemplos, se muestra cómo luce un texto de varias líneas con varios parámetros de configuración de LineHeightStyle.Trim
cuando la alineación está centrada (LineHeightStyle.Alignment.Center
).
![]() |
![]() |
LineHeightStyle.Trim.None | LineHeightStyle.Trim.Both |
![]() |
![]() |
LineHeightStyle.Trim.FirstLineTop | LineHeightStyle.Trim.LastLineBottom |
Consulta la entrada de blog para corregir el padding de fuentes en Compose Text a fin de obtener más información sobre el contexto de este cambio, el funcionamiento de includeFontPadding en el sistema Vista y los cambios que realizamos en Compose y las nuevas APIs de LineHeightStyle
.
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
Según la configuración 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")
}
}
Puede que quieras inhabilitar la selección en partes específicas de un área seleccionable. Para hacerlo, debes unir la parte que no se puede seleccionar 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")
}
}
}
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
que admite composición (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 a fin de 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
:
TextField
es la implementación de Material Design. Te recomendamos que elijas esta implementación, ya que sigue los lineamientos de Material Design:BasicTextField
permite a los usuarios editar texto con 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") }
)
}
@Composable
fun SimpleOutlinedTextFieldSample() {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
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)
)
}
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
Formato
TextField
te permite configurar un VisualTransformation
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)
)
}
Puedes encontrar más ejemplos en el código fuente de VisualTransformSamples.
Cómo limpiar la información de entrada
Una tarea común a la hora de editar texto es quitar caracteres iniciales o transformar la string de entrada cada vez que cambia.
Como modelo, debes suponer que el teclado puede realizar ediciones arbitrarias y grandes en cada instancia de onValueChange
. Esto puede ocurrir, por ejemplo, si el usuario usa la autocorrección, reemplaza una palabra por un emoji o utiliza alguna otra función de edición inteligente. Para solucionar esto de forma correcta, escribe cualquier lógica de transformación suponiendo que el texto actual que se pasará a onValueChange
no está relacionado con los valores anteriores o siguientes que se pasarán a onValueChange
.
A fin de implementar un campo de texto que no permita ceros iniciales, puedes quitar todos los ceros iniciales en cada cambio de valor.
@Composable
fun NoLeadingZeroes() {
var input by rememberSaveable { mutableStateOf("") }
TextField(
value = input,
onValueChange = { newText ->
input = newText.trimStart { it == '0' }
}
)
}
Para controlar la posición del cursor mientras limpias texto, usa la sobrecarga de TextFieldValue
de TextField
como parte del estado.
Fuentes descargables
A partir de Compose 1.2-alpha07, puedes usar la API de fuentes descargables en tu app de Compose para descargar fuentes de Google de forma asíncrona y usarlas en tu app.
Por el momento, no se admiten las fuentes descargables que proporcionan los proveedores personalizados.
Cómo usar las fuentes descargables de manera programática
Para descargar una fuente de manera programática desde tu app, sigue estos pasos:
- Agrega la dependencia:
Groovy
dependencies { ... implementation "androidx.compose.ui:ui-text-google-fonts:1.3.0" }
Kotlin
dependencies { ... implementation("androidx.compose.ui:ui-text-google-fonts:1.3.0") }
- Inicializa el objeto
GoogleFont.Provider
con las credenciales de Google Fonts.@OptIn(ExperimentalTextApi::class) val provider = GoogleFont.Provider( providerAuthority = "com.google.android.gms.fonts", providerPackage = "com.google.android.gms", certificates = R.array.com_google_android_gms_fonts_certs )
Los parámetros que recibe el proveedor son los siguientes:- La autoridad del proveedor de fuentes de Google Fonts
- El paquete del proveedor de fuentes para verificar su identidad
- Una lista de conjuntos de hashes para los certificados a fin de verificar la identidad del proveedor (puedes encontrar los valores hash necesarios para el proveedor de Google Fonts en el archivo
font_certs.xml
, en la app de ejemplo de JetChat)
ExperimentalTextApi
para poder usar las API de fuentes descargables en tu app. - Define un objeto
FontFamily
de la siguiente manera:import androidx.compose.ui.text.googlefonts.GoogleFont import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider) )
Puedes consultar otros parámetros para tu fuente, como el grosor y el estilo, conFontWeight
yFontStyle
, respectivamente:import androidx.compose.ui.text.googlefonts.GoogleFont import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold, style = FontStyle.Italic) )
- Configura el
FontFamily
que se usará en la función de componibilidad de texto, y eso es todo.Text( fontFamily = fontFamily, text = "Hello World!" )
FontFamily
.val MyTypography = Typography( body1 = TextStyle( fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = ... ), body2 = TextStyle( fontFamily = fontFamily, fontWeight = FontWeight.Bold, letterSpacing = ... ), h4 = TextStyle( fontFamily = fontFamily, fontWeight = FontWeight.SemiBold ... ), ...
Luego, establece la tipografía en el tema de tu app:MyAppTheme( typography = MyTypography ) { ...
Para ver un ejemplo de una app que implementa fuentes descargables en Compose junto con Material3, asegúrate de revisar la app de ejemplo de JetChat.
Fuentes de resguardo
Puedes determinar una cadena de resguardo para tu fuente, en caso de que esta no se descargue correctamente. Por ejemplo, si tienes una fuente descargable definida de la siguiente manera:
import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider), Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold) )
Puedes definir los valores predeterminados de la fuente para ambos grosores, de la siguiente manera:
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider), Font(resId = R.font.my_font_regular), Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold), Font(resId = R.font.my_font_regular_bold, weight = FontWeight.Bold) )
Asegúrate de agregar las importaciones correctas.
Cuando defines FontFamily
de esta manera, se crea una FontFamily
que contiene dos cadenas, una por cada grosor. El mecanismo de carga intentará resolver la fuente en línea primero y, luego, la fuente ubicada en la carpeta de recursos R.font
locales.
Cómo depurar tu implementación
Para ayudarte a verificar si la fuente se descarga correctamente, puedes definir un controlador de corrutinas de depuración. Tu controlador proporciona el comportamiento de lo que se debe hacer en caso de que la fuente no se cargue de forma asíncrona.
Para comenzar, crea un CoroutineExceptionHandler
.
val handler = CoroutineExceptionHandler { _, throwable ->
// process the Throwable
Log.e(TAG, "There has been an issue: ", throwable)
}
Luego, pásalo al método createFontFamilyResolver
para que el agente de resolución use el nuevo controlador:
CompositionLocalProvider(
LocalFontFamilyResolver provides createFontFamilyResolver(LocalContext.current, handler)
) {
Column {
Text(
text = "Hello World!",
style = MaterialTheme.typography.body1
)
}
}
También puedes usar la API de isAvailableOnDevice
del proveedor para probar si está disponible y si los certificados están configurados de forma correcta. Para ello, puedes llamar al método isAvailableOnDevice
, que muestra el valor "false" si el proveedor no está bien configurado.
val context = LocalContext.current
LaunchedEffect(Unit) {
if (provider.isAvailableOnDevice(context)) {
Log.d(TAG, "Success!")
}
}
Advertencias
Google Fonts tarda varios meses en habilitar las nuevas fuentes en Android.
Existe un intervalo de tiempo entre el momento en que se agrega una fuente en fonts.google.com y el momento en que está disponible desde la API de fuentes descargables (ya sea en el sistema de vistas o en Compose). Es posible que las fuentes agregadas recientemente no se carguen en tu app con una IllegalStateException
.
Para ayudar a los desarrolladores a identificar este error entre otros tipos de errores de carga de fuentes, agregamos mensajes descriptivos de la excepción en Compose con los cambios aquí.