En este tutorial verás el uso de texto en Compose a partir de la función componible Text
. Estudiaremos el propósito de cada uno de sus parámetros, con el fin de cambiar características como tamaño, color, alineación de escritura, fuente, etc.
Ejemplo De Texto En Compose
La idea es explorar el cambio del texto cuando modificamos los argumentos con que es creado en la recomposición. Por lo que crearemos un archivo llamado Text.kt
, donde añadiremos múltiples funciones componibles con previsualizaciones para experimentar estos cambios.
Esta serie de ejemplos puedes encontrarlo en el módulo p7_components
del proyecto Android Studio de la guía de Jetpack Compose:
La Función Text
A lo largo de los tutoriales anteriores, hemos invocado a Text()
gran cantidad de veces para mostrar texto en pantalla de forma básica.
En esta ocasión entraremos en detalle en su firma para conseguir resultados que puedan ser útiles en situaciones que requieran proyectar texto más estilizado.
La siguiente es su definición formal en la documentación de Jetpack Compose:
@Composable
fun Text(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
)
Veamos como funciona cada uno.
Mostrar Texto En Pantalla
Como ya sabes, el parámetro text
recibe un String
que será desplegado en la pantalla. Debido a que los demás parámetros tienen valores por defecto, es posible llamar a Text
pasando un texto básico como el siguiente:
@Composable
fun ShowText(){
Text(text = "Texto En Compose")
}
También puedes usar un recurso de texto para mostrar texto en pantalla. Para ello cárgalo con la función stringResource()
y pasa como argumento el indicador de la clase R
:
@Composable
fun ShowTextFromResources() {
Column {
Text(stringResource(R.string.string_res))
Text(stringResource(R.string.format_string_res, 2))
for (item in stringArrayResource(R.array.string_array_res)) {
Text(item)
}
}
}
La función stringResource()
también te permite cargar strings con formato. E incluso tenemos a stringArrayResource()
para cargar arrays de strings. El ejemplo anterior usa los siguientes elementos <string>
y <array-string>
:
<resources>
<string name="app_name">Texto En Compose</string>
<string name="string_res">Texto desde strings.xml</string>
<string name="format_string_res">Cantidad de ítems: %1$s</string>
<string-array name="string_array_res">
<item>String 1</item>
<item>String 2</item>
<item>String 3</item>
</string-array>
</resources>
Cambiar Color De Texto
Pasa una instancia Color
al atributo color
para cambiar el color de todo el texto:
@Composable
fun TextColor() {
Text("Color Cyan", color = Color.Cyan)
}
Cambiar Tamaño De Texto
El tamaño de texto en Compose es representado por la clase TextUnit
. Las unidades de medida disponibles son: sp
, em
y Unspecified
(toma valor por defecto o hereda del tema aplicado).
Por ejemplo: Mostrar dos textos con tamaños de 20sp y 10em.
@Composable
fun TextSize() {
Column {
Text("Texto con 20sp", fontSize = 20.sp)
Text("Texto con 10em", fontSize = 10.em)
}
}
Mostrar Texto En Cursiva
Los estilos de fuente son generados por el argumento que le pases a fontStyle
. La clase FontStyle
define si un texto está en cursiva o normal.
Ejemplo:
@Composable
fun ItalicText() {
Text("Texto en cursiva", fontStyle = FontStyle.Italic)
}
Mostrar Texto En Negrilla
Controla el grosor de todo el texto con el parámetro fontWeight
. Los valores que recibe se encuentran en la clase FontWeight
. Puedes usar las propiedades W100
hasta W900
o sus alias Thin
hasta Black
.
Por ejemplo: Mostrar un texto con el peso numérico 500 y otro con Extra Bold.
@Composable
fun FontWeightText() {
Column {
Text("Texto con grosor W500", fontWeight = FontWeight.W500)
Text("Texto con grosor Extra Bold", fontWeight = FontWeight.ExtraBold)
}
}
Cambiar Familia Tipográfica
Modifica la familia tipográfica con el parámetro fontFamily
a través de la creación de un objeto FontFamily
con múltiples componentes TextStyle
.
Por ejemplo: Renderizar un texto con la familia tipográfica Besley y varios de sus grosores.
En primer lugar debes descargar los archivos de la fuente en el sitio del creador (Google Fonts para este ejemplo) y luego incluirlos en src/font
:
Esto te habilita acceder a la referencias, como R.font.besley_regular
, para la creación de la familia:
@Composable
fun FontFamilyText() {
val besleyFontFamily = FontFamily(
Font(R.font.besley_regular, FontWeight.Normal),
Font(R.font.besley_medium, FontWeight.Medium),
Font(R.font.besley_semibold, FontWeight.SemiBold),
Font(R.font.besley_bold, FontWeight.Bold),
Font(R.font.besley_extrabold, FontWeight.ExtraBold),
Font(R.font.besley_black, FontWeight.Black)
)
Column {
Text(
"Besley Normal",
fontFamily = besleyFontFamily,
fontWeight = FontWeight.Normal
)
Text(
"Besley Medium",
fontFamily = besleyFontFamily,
fontWeight = FontWeight.Medium
)
Text(
"Besley Semi-bold",
fontFamily = besleyFontFamily,
fontWeight = FontWeight.SemiBold
)
Text(
"Besley Bold",
fontFamily = besleyFontFamily,
fontWeight = FontWeight.Bold
)
Text(
"Besley Extra-bold",
fontFamily = besleyFontFamily,
fontWeight = FontWeight.ExtraBold
)
Text(
"Besley Black",
fontFamily = besleyFontFamily,
fontWeight = FontWeight.Black
)
}
}
Modificar Espaciado Entre Caracteres
Otra característica que puedes alterar es el espacio que existe entre los caracteres del texto con letterSpacing
. Asigna valores de la clase TextUnit
con la cantidad que deseas aplicar.
Ejemplo: Contrastar el espacio entre letras de un texto cuando se aplican las cantidades 0.15em y 0.4em:
@Composable
fun LetterSpacingText() {
Column {
Text("Texto", letterSpacing = 0.15.em)
Text("Texto", letterSpacing = 0.4.em)
}
}
Subrayar/Tachar Un Texto
El parámetro textDecoration
te posibilita subrayar o tachar un texto a partir de las propiedades de la clase TextDecoration
. Esta define el dibujo de una línea horizontal sobre el texto.
Por ejemplo: Crear un texto subrayado, otro tachado y otro con la combinación de ambas líneas:
@Composable
fun TextDecorationExample() {
Column {
Text("Texto", textDecoration = TextDecoration.Underline)
Text("Texto", textDecoration = TextDecoration.LineThrough)
Text(
"Texto",
textDecoration = TextDecoration.Underline + TextDecoration.LineThrough
)
}
}
Justificar Texto O Alinear A Izquierda, Centro O Derecha
Si deseas cambiar la alineación del texto dentro de las líneas de un párrafo usa el parámetro textAlign
. Este recibe valores de la clase TextAlign
, la cual posee las siguientes propiedades para representar la alienación:
Center
End
Justify
Left
Right
Start
El propósito de Start
y End
sigue siendo el mismo que hemos visto en el sistema de views. Representan el lado inicial y final sin importar cual sea la dirección de escritura del lenguaje que está siendo usado en la App.
Center
alinea el texto en el centro del contenedor y Justify
estira las líneas de texto para llenar el ancho del contenedor a través de saltos de líneas automáticos.
Ejemplo: Comparar las diferencias entre un párrafo alineado a la izquierda, al centro, a la derecha y con justificación:
@Composable
fun TextAlignExample() {
Column {
val paragraph = stringResource(R.string.paragraph_res)
val width = Modifier.width(300.dp)
Text(text = paragraph, textAlign = TextAlign.Start, modifier = width)
Space()
Text(text = paragraph, textAlign = TextAlign.Center, modifier = width)
Space()
Text(text = paragraph, textAlign = TextAlign.End, modifier = width)
Space()
Text(text = paragraph, textAlign = TextAlign.Justify, modifier = width)
}
}
@Composable
private fun Space() {
Spacer(modifier = Modifier.size(16.dp))
}
Modificar Altura De La Línea Del Texto
La altura de línea es el área vertical que recubre la línea de un texto. Es decir, la suma entre el área del contenido del texto, más su espacio de salto con respecto a la siguiente:
Para aumentar o disminuir este valor pasa una instancia TextUnit
al parámetro lineHeight
de Text
.
Ejemplo: Dados tres párrafos, reflejar su presentación cuando con alturas de línea de 20dp y 30dp.
@Composable
fun LineHeightExample() {
Row {
Text("LineHeight = 20sp\nPárrafo\nPárrafo", lineHeight = 20.sp)
Space()
Text("LineHeight = 30sp\nPárrafo\nPárrafo", lineHeight = 30.sp)
}
}
Truncar Texto Desbordado
Si el tamaño del texto es tan largo que excede las restricciones impuestas por su contenedor, se producirán desbordamientos que puede que afecten el espacio de otros componentes aledaños.
Para evitar esto, usa el parámetro overflow. Este recibe los siguientes valores de la clase TextOverflow
:
Clip
(valor por defecto): Corta el texto de forma precisa para ajustarlo a su contenedorEllipsis
: Suprime los últimos caracteres del texto con una elipsis para indicar el desbordamientoVisible
: Despliega todo el texto incluso si no hay espacio para albergarlo en su contenedor
Ejemplo: Mostrar las diferentes formas de manejar el desbordamiento de un texto con tres líneas.
@Composable
fun OverflowExample() {
Box(modifier = Modifier.width(300.dp)) {
Column(Modifier.width(200.dp)) {
TitleExample("Clip")
Text(
"Este texto excede el tamaño de 200dp",
maxLines = 1
)
Space()
TitleExample("Ellipsis")
Text(
"Este texto excede el tamaño de 200dp",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Space()
TitleExample("Visible")
Text(
"Este texto excede el tamaño de 200dp",
overflow = TextOverflow.Visible,
softWrap = false
)
}
}
}
Saltos De Línea Automáticos
Cuando Compose renderiza un componente Text
cuyo contenido de texto es más largo que el ancho de su contenedor, este es envuelto (soft wrap) a fin de dividir su continuación en la siguiente línea.
Este aspecto está habilitado por defecto con el parámetro booleano softWrap
, quien recibe true en la definición de la función. No obstante, si quieres que las líneas de texto se desplieguen como si no existiese límite horizontal, entonces pasa false
.
Ejemplo: Evitar que un texto tenga saltos de línea automáticos cuando es presentado en pantalla:
@Composable
fun SoftWrapExample() {
Row(Modifier.width(200.dp)) {
Text(
"Texto largo sin saltos de línea",
softWrap = false,
modifier = Modifier.weight(1f)
)
Space()
Text(
"Otro texto",
modifier = Modifier.weight(1f)
)
}
}
Limitar Cantidad De Líneas De Un Texto
El número de líneas de texto que aceptará un componente Text
es definido por el valor del atributo maxLines
. Pasa valores enteros mayores que cero para limitar el máximo de líneas que requieres en tu texto.
Ejemplo: Permitir máximo tres líneas en un texto.
@Composable
fun MaxLinesExample() {
Column {
TitleExample("maxLines = 3")
Text("Párrafo\n".repeat(4), maxLines = 3)
}
}
Realizar Acciones Cuando El Texto Se Muestra
Existe otro parámetro de tipo función (TextLayoutResult) -> Unit
llamado onTextLayout
, que te permite especificar sentencias una vez el layout del texto ha sido calculado.
La clase de datos TextLayoutResult
almacena todos los aspectos relevantes del texto creado como párrafos, tamaño y la configuración de su estilo.
Por lo que te da la oportunidad de pasar una lambda para agregar nuevos detalles o funcionalidades al texto.
Ejemplo: Imprimir el número de líneas resultantes que tiene un texto en 150dp.
@Composable
fun OnTextLayoutExample() {
var lines: String by remember {
mutableStateOf("X")
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(Modifier.width(150.dp)) {
TitleExample("onTextLayout")
Text(
"¿Cuántas líneas se generan para este texto a 150dp?",
onTextLayout = {
lines = "R:/${it.lineCount}"
})
Text(lines)
}
}
}
Lectura: El ejemplo anterior usó un estado mutable para sostener el número de líneas creado. Aprende más leyendo Estado En Compose.
Aplicar Un Tema A Un Texto
Esta característica puedes verla en mi tutorial Aplicar Temas En Compose. Ahí verás como el parámetro style
de Text
puede recibir un tipo de escala basado en el sistema de Material Design.
Por ejemplo: Aplicar la escala Headline 6 de la tipografía base de Material Design:
@Composable
private fun TitleExample(text: String) {
Text(text = text, style = MaterialTheme.typography.h6)
}
Usar Múltiples Estilos A Un Texto
El componente Text
tiene otra variante donde el parámetro text
es de tipo AnnotatedString
. Esta clase representa la estructura de un texto con múltiples estilos aplicados.
¿De que se compone esta definición?
- Un
String
que representa el texto - Una lista de elementos
SpanStyle
que definen el estilo para uno o más caracteres - Una lista de elementos
ParagraphStyle
para especificar el estilo de un párrafo completo
Aplicar Estilo A Diferentes Caracteres
Hay tres formas para construir las instancias AnnotatedString
.
La primera de ellas es usar su constructor público:
AnnotatedString(
text: String,
spanStyles: List<AnnotatedString.Range<SpanStyle>> = listOf(),
paragraphStyles: List<AnnotatedString.Range<ParagraphStyle>> = listOf()
)
Como ves, spanStyles
y paragraphStyles
son listas del tipo Range
. Este representa la información de inicio y final asociada al estilo. Por lo que para crear rangos pasamos el estilo y las posiciones a las que se aplicará:
<T : Any?> Range(item: T, start: Int, end: Int)
Por ejemplo:
Tomar la palabra «Develou» y aplicar los siguientes estilos:
- Caracteres [1,2] -> Color amarillo + tamaño 24sp
- Caracteres [3,4] -> Color azul + tamaño 16sp
- Caracteres [5-7] -> Color rojo + tamaño 12sp
Usando SpanStyle
tendrás:
@Composable
fun SpanStyleExample() {
val styles = listOf(
AnnotatedString.Range(SpanStyle(Color.Yellow, fontSize = 24.sp), 0, 2),
AnnotatedString.Range(SpanStyle(Color.Blue, fontSize = 16.sp), 2, 4),
AnnotatedString.Range(SpanStyle(Color.Red, fontSize = 12.sp), 4, 7)
)
Text(AnnotatedString("Develou", styles))
}
El AnnotatedString.Builder
Otra forma es usar AnnotatedString.Builder
y construir paso a paso la cadena. El ejemplo anterior es posible representarlo así:
@Composable
fun SpanStyleExample() {
Text(
with(AnnotatedString.Builder("Develou")) {
addStyle(SpanStyle(Color.Yellow, fontSize = 24.sp), 0, 2)
addStyle(SpanStyle(Color.Blue, fontSize = 16.sp), 2, 4)
addStyle(SpanStyle(Color.Red, fontSize = 12.sp), 4, 7)
toAnnotatedString()
}
)
}
La instancia del builder se construye con un string adicional sobre el cual puedes invocar los siguientes métodos:
addStyle()
: Aplica unSpanStyle
oParagraphStyle
a un segmento del string actualappend()
: Concatena texto al valor inicialpushStyle()
: Aplica unSpanStyle
oParagraphStyle
a todos los elementos que añadas hasta que llames apop()
pop()
: Finaliza el último estilo o anotación iniciado
La Función buildAnnotatedString()
Adicionalmente, existe la función buildAnnotatedString()
que usa al Builder
anterior para proporcionarte un DSL más intuitivo a la hora de crear los bloques de construcción.
Al invocar esta función las sentencias se verían así:
@Composable
fun SpanStyleExample() {
Text(
buildAnnotatedString {
withStyle(SpanStyle(Color.Yellow, fontSize = 24.sp)) {
append("De")
}
withStyle(SpanStyle(Color.Blue, fontSize = 16.sp)) {
append("ve")
}
withStyle(SpanStyle(Color.Red, fontSize = 12.sp)) {
append("lou")
}
}
)
}
Donde withStyle()
recibe una instancia de SpanStyle
o ParagraphStyle
para aplicarla sobre los bloques internos. Y append()
concatena el texto con el estilo al resultado final.
Aplicar Estilos A Un Párrafo
En el caso en que quieras aplicarle el estilo a todas las líneas de un párrafo usa ParagraphStyle
en cualquier modo de construcción.
Por ejemplo:
Probar los cuatro parámetros de ParagraphStyle
: lineHeight, textDirection, textAlign y textIndent.
@Composable
fun ParagraphStyleExample() {
Text(
buildAnnotatedString {
withStyle(SpanStyle(fontSize = 20.sp)) {
append("lineHeight (20sp)")
}
withStyle(ParagraphStyle(lineHeight = 24.sp)) {
append("En este texto aplicamos 24sp para la altura de línea\n")
}
withStyle(SpanStyle(fontSize = 20.sp)) {
append("textDirection (Rtl)")
}
withStyle(ParagraphStyle(textDirection = TextDirection.Rtl)) {
append("En este texto aplicamos una dirección de texto de derecha a izquierda\n")
}
withStyle(SpanStyle(fontSize = 20.sp)) {
append("textAlign (Center)")
}
withStyle(ParagraphStyle(textAlign = TextAlign.Center)) {
append("Este texto está alineado al centro\n")
}
withStyle(SpanStyle(fontSize = 20.sp)) {
append("textIndent (20sp)")
}
withStyle(ParagraphStyle(textIndent = TextIndent(20.sp, 8.sp))) {
append("En este texto aplicamos 16sp de sangría para la primera línea ")
append("y 8sp para las demás.")
}
},
modifier = Modifier
.width(200.dp)
.padding(16.dp)
)
}
Permitir Al Usuario Seleccionar Texto
Si deseas habilitar la selección de texto cuando el usuario presiona prolongadamente una línea del mismo, entonces recubre tu componente Text
con SelectionContainer()
.
Por ejemplo:
@Composable
fun SelectionContainerExample() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
SelectionContainer {
Text("Este texto puede ser seleccionado")
}
}
}
Si deseas excluir un componente de la selección de texto, entonces recúbrelo con la función componible DisableSelection()
.
@Composable
fun SelectionContainerExample() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
SelectionContainer {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Este texto puede ser seleccionado")
DisableSelection {
Text("Esto no")
}
Text("Desde aquí vuelve a ser seleccionable")
}
}
}
}
Obtener La Posición Cliqueada De Un Texto
Cuando desees conseguir el lugar del texto en que el usuario ha realizado clic, usa el componente ClickableText
en vez Text
.
Este recibe como parámetro una función (Int) -> Unit
que te provee la posición seleccionada.
Por ejemplo:
@Composable
fun ClickableTextExample() {
var clickPos by remember {
mutableStateOf(0)
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
ClickableText(
text = AnnotatedString("Posición cliqueada -> $clickPos"),
style = MaterialTheme.typography.h6
) { offset ->
clickPos = offset
}
}
}
Añadir URL A Texto
La clase AnnotatedString
te permite añadir anotaciones a las secciones del texto, con el fin de describir su contenido y especificar alguna lógica adicional.
Una de las aplicaciones más comunes de anotaciones es enlazar un segmento de texto a una URL. Donde añades el estilo para el enlace y luego, junto a ClickableText
, determinas si la anotación es parte de ese fragmento para abrir el navegador web.
Por ejemplo:
@Composable
fun TextUrlExample() {
val text = buildAnnotatedString {
append("Visita mi sitio ")
pushStringAnnotation("URL", "https://www.develou.com")
withStyle(
SpanStyle(
color = Color.Blue,
fontWeight = FontWeight.Bold
)
) {
append("develou.com")
}
pop()
}
val uriHandler = LocalUriHandler.current
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
ClickableText(text, style = MaterialTheme.typography.body2) { offset ->
text.getStringAnnotations(
tag = "URL",
start = offset,
end = offset
).firstOrNull()?.let { annotation ->
uriHandler.openUri(annotation.item)
}
}
}
}
El método pushStringAnnotation()
facilita la inclusión de la anotación a partir del método Builder.appendAnnotation()
.
Cuando el usuario haga clic, podrás obtener la anotación con AnnotatedString.getStringAnnotations()
con la etiqueta de referencia («URL» para este ejemplo) y el lugar donde se hizo click pasando a offset
al límite [start
, end
].
Por otro lado, la propiedad de composición local LocalUriHandler
te permite abrir la URL en la App del navegador. Por lo que le pasas el valor de la propiedad item
al método openUri()
.
Únete Al Discord De Develou
Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!