Lección 1: Funciones que admiten composición
Jetpack Compose se basa en funciones que admiten composición. Estas funciones te permiten definir la IU de tu app de manera programática, ya que describen su diseño y brindan dependencias de datos, en lugar de enfocarse en el proceso de construcción de la IU (inicializar un elemento, adjuntarlo a un elemento superior, etc.). Para crear una función que admita composición, agrega la anotación @Composable
al nombre de la función.
Agrega un elemento de texto
Para empezar, descarga la versión más reciente de Android Studio y crea una app. Para ello, selecciona New Project y En la categoría Phone and Tablet, selecciona Empty Activity. Nombre tu app ComposeTutorial y haz clic en Finish. La plantilla predeterminada ya contiene algunos elementos de Compose, pero en este instructivo vamos a compilarla paso a paso.
Primero, muestra el mensaje "Hello World!". Para tal fin, agrega un elemento de texto dentro del método onCreate
. Debes definir un bloque de contenido y llamar a la función de componibilidad Text
. El bloque setContent
define el diseño de la actividad, donde se llaman a las funciones que admiten composición. Estas funciones solo se pueden llamar desde otras funciones del mismo tipo.
Jetpack Compose usa un complemento de compilador de Kotlin para transformar estas funciones que admiten composición en elementos de la IU de la app. Por ejemplo, la función de componibilidad Text
y que está definida por la biblioteca de la IU de Compose muestra una etiqueta de texto en la pantalla.
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.Text class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text("Hello world!") } } }
Define una función de componibilidad
A fin de que una función admita composición, debes agregar la anotación @Composable
.
Para probarlo, define una función MessageCard
a la que se le da un nombre que usa para configurar el elemento de texto.
// ... import androidx.compose.runtime.Composable class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard("Android") } } } @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") }
Obtén una vista previa de tu función en Android Studio
La anotación @Preview
te permite obtener una vista previa de tus funciones que admiten composición dentro de Android Studio sin tener que compilar e instalar la app en un emulador o dispositivo Android. La anotación se debe usar en una función de componibilidad y que no acepte parámetros. Por este motivo, no puedes obtener una vista previa de la función MessageCard
directamente. En su lugar, crea una segunda función llamada PreviewMessageCard
, que llama al objeto MessageCard
con un parámetro adecuado. Agrega la anotación @Preview
antes del elemento @Composable
.
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }
Vuelve a compilar tu proyecto. La app en sí misma no cambia, ya que no se llama a la nueva función PreviewMessageCard
, pero Android Studio agrega una ventana de vista previa que puedes expandir si haces clic en la vista dividida (design/code). Esta ventana muestra una vista previa de los elementos de IU creados por funciones que admiten composición marcadas con la anotación @Preview
. Para actualizar las vistas previas en cualquier momento, haz clic en el botón para actualizar en la parte superior de la ventana de vista previa.
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.Text class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text("Hello world!") } } }
// ... import androidx.compose.runtime.Composable class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard("Android") } } } @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") }
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }
Lección 2: Diseños
Los elementos de la IU son jerárquicos, ya que unos contienen a otros. En Compose, compilas una jerarquía de la IU llamando a las funciones que admiten composición desde otras funciones del mismo tipo.
Agrega varios textos
Hasta ahora, compilaste tu primera función de componibilidad y obtuviste una vista previa. Para descubrir otras capacidades de Jetpack Compose, vas a compilar una pantalla de mensajes simple que contiene una lista de mensajes expandible con algunas animaciones.
Comienza por mejorar el contenido de los mensajes mostrando su autor y contenido. Primero, debes cambiar el parámetro componible para que acepte un objeto Message
en lugar de una String
y agregar otro elemento Text
componible dentro de MessageCard
(que también es componible). También asegúrate de actualizar la vista previa.
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard(Message("Android", "Jetpack Compose")) } } } data class Message(val author: String, val body: String) @Composable fun MessageCard(msg: Message) { Text(text = msg.author) Text(text = msg.body) } @Preview @Composable fun PreviewMessageCard() { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) }
Este código crea dos elementos de texto dentro de la vista de contenido. Sin embargo, como no brindaste ninguna información sobre cómo organizarlos, los dos elementos de texto se dibujan uno encima del otro, lo cual hace que el texto sea ilegible.
Usa una columna
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }
Agrega un elemento de imagen
Para enriquecer la tarjeta de mensajes, agrega la foto de perfil del remitente. Puedes usar Resource Manager para importar una imagen de tu biblioteca de fotos, o bien utilizar esta. Agrega un elemento Row
que admita composición para lograr un diseño bien estructurado y, dentro de él, una Image
que se pueda componer.
// ... import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Row import androidx.compose.ui.res.painterResource @Composable fun MessageCard(msg: Message) { Row { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", ) Column { Text(text = msg.author) Text(text = msg.body) } } }
Configura tu diseño
El diseño del mensaje está bien estructurado, pero sus elementos no están bien espaciados y la imagen es demasiado grande. Para decorar o configurar un elemento componible, Compose usa modificadores, que te permiten cambiar su tamaño, diseño y apariencia, o agregar interacciones de alto nivel, como hacer que un elemento sea apto para recibir clics. Puedes encadenarlos a fin de crear elementos componibles más completos. Con ellos, podrás mejorar el diseño.
// ... import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp @Composable fun MessageCard(msg: Message) { // Add padding around our message Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", modifier = Modifier // Set image size to 40 dp .size(40.dp) // Clip image to be shaped as a circle .clip(CircleShape) ) // Add a horizontal space between the image and the column Spacer(modifier = Modifier.width(8.dp)) Column { Text(text = msg.author) // Add a vertical space between the author and message texts Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard(Message("Android", "Jetpack Compose")) } } } data class Message(val author: String, val body: String) @Composable fun MessageCard(msg: Message) { Text(text = msg.author) Text(text = msg.body) } @Preview @Composable fun PreviewMessageCard() { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) }
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }
// ... import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Row import androidx.compose.ui.res.painterResource @Composable fun MessageCard(msg: Message) { Row { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", ) Column { Text(text = msg.author) Text(text = msg.body) } } }
// ... import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp @Composable fun MessageCard(msg: Message) { // Add padding around our message Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", modifier = Modifier // Set image size to 40 dp .size(40.dp) // Clip image to be shaped as a circle .clip(CircleShape) ) // Add a horizontal space between the image and the column Spacer(modifier = Modifier.width(8.dp)) Column { Text(text = msg.author) // Add a vertical space between the author and message texts Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }
Lección 3: Material Design
Compose está diseñado para admitir los principios de Material Design. Muchos de sus elementos de la IU implementan Material Design directamente. En esta lección, diseñarás tu app con widgets de Material.
Usa Material Design
Ahora, tu mensaje tiene un diseño, pero aún no se ve muy bien.
Jetpack Compose ofrece una implementación de Material Design 3 con elementos de la IU listos para usar. Mejorarás el aspecto del elemento componible MessageCard
con los estilos de Material Design.
Para comenzar, une la función MessageCard
con el tema de Material que creaste en tu proyecto, ComposeTutorialTheme
, así como con un elemento Surface
.
Hazlo tanto en @Preview
como en la función setContent
. Si lo haces, los elementos componibles heredan estilos según lo definido en el tema de tu app, lo que garantiza la coherencia entre la app.
Material Design se basa en tres pilares: Color
, Typography
y Shape
.
Los agregarás uno por uno.
Nota: La plantilla de actividad de Compose vacía genera un tema predeterminado para el proyecto que te permite personalizar
MaterialTheme
.
Si le asignaste un nombre que no sea ComposeInstructivo, puedes encontrar el tema que personalizaste en el archivo Theme.kt
, en el subpaquete ui.theme
.
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Surface(modifier = Modifier.fillMaxSize()) { MessageCard(Message("Android", "Jetpack Compose")) } } } } } @Preview @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!") ) } } }
Color
Usa MaterialTheme.colorScheme
para aplicar estilos con los colores del tema unido. Puedes usar estos valores del tema siempre que necesites un color. En este ejemplo, se usan colores de temas dinámicos (definidos por las preferencias del dispositivo).
Puedes configurar dynamicColor
como false
en el archivo MaterialTheme.kt
para cambiar esto.
Cambia el estilo del título y agrega un borde a la imagen.
// ... import androidx.compose.foundation.border import androidx.compose.material3.MaterialTheme @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary ) Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }
Tipografía
Los estilos de tipografía de Material están disponibles en MaterialTheme
; solo tienes que agregarlos a los elementos componibles Text
.
// ... @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Text( text = msg.body, style = MaterialTheme.typography.bodyMedium ) } } }
Forma
Con Shape
, puedes agregar los toques finales. Primero, une el texto del cuerpo del mensaje con un elemento
Surface
que admita composición. Esto permite personalizar la forma y la elevación del cuerpo del mensaje. También se agrega padding al mensaje a fin de mejorar el diseño.
// ... import androidx.compose.material3.Surface @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), style = MaterialTheme.typography.bodyMedium ) } } } }
Habilitar el tema oscuro
Se puede habilitar el tema oscuro (o modo nocturno) para reducir el brillo de la pantalla, en especial durante la noche, o simplemente para ahorrar batería en el dispositivo. Gracias a su compatibilidad con Material Design, Jetpack Compose puede manejar el tema oscuro de forma predeterminada. Si usas los colores de Material Design, el texto y el fondo se adaptarán automáticamente al fondo oscuro.
Puedes crear varias vistas previas en tu archivo como funciones independientes o agregar varias anotaciones a la misma función.
Agrega una anotación de vista previa nueva y habilita el modo nocturno.
// ... import android.content.res.Configuration @Preview(name = "Light Mode") @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" ) @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) } } }
Las opciones de color de los temas claro y oscuro se definen en el archivo Theme.kt
generado por IDE.
Hasta ahora, creaste un elemento de la IU para mensajes que muestra una imagen y dos textos con distintos estilos, y se ve bien en temas claros y oscuros.
// ... import android.content.res.Configuration @Preview(name = "Light Mode") @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" ) @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) } } }
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Surface(modifier = Modifier.fillMaxSize()) { MessageCard(Message("Android", "Jetpack Compose")) } } } } } @Preview @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!") ) } } }
// ... import androidx.compose.foundation.border import androidx.compose.material3.MaterialTheme @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary ) Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }
// ... @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Text( text = msg.body, style = MaterialTheme.typography.bodyMedium ) } } }
// ... import androidx.compose.material3.Surface @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), style = MaterialTheme.typography.bodyMedium ) } } } }
// ... import android.content.res.Configuration @Preview(name = "Light Mode") @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" ) @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) } } }
Lección 4: Listas y animaciones
Las listas y las animaciones están presentes en todos lados dentro de las apps. En esta lección, aprenderás cómo Compose hace que crear listas sea más fácil y que agregar animaciones sea más divertido.
Crea una lista de mensajes
Tener un solo mensaje en el chat parece muy poco, por lo que cambiaremos la conversación para que tenga más de un mensaje. Deberás crear una función Conversation
que muestre varios mensajes. Para este caso de uso, utiliza las
LazyColumn
y
LazyRow
de Compose. Estos elementos componibles solo renderizan los objetos visibles en la pantalla, por lo que están diseñados para ser muy eficientes con listas largas.
En este fragmento de código, puedes ver que LazyColumn
tiene un elemento secundario items
. Este objeto toma una List
como parámetro y su lambda recibe un parámetro que llamamos message
(podríamos haberle asignado cualquier nombre), que es una instancia de Message
.
En resumen, se llama a esta lambda para cada elemento de la List
proporcionada. Copia el conjunto de datos de muestra en tu proyecto para ayudar a iniciar la conversación con rapidez.
// ... import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @Composable fun Conversation(messages: List<Message>) { LazyColumn { items(messages) { message -> MessageCard(message) } } } @Preview @Composable fun PreviewConversation() { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } }
Agrega animaciones cuando se expandan los mensajes
La conversación es cada vez más interesante. Es hora de jugar con las animaciones. Agregarás la posibilidad de expandir un mensaje para poder mostrar uno más largo y animaremos el tamaño del contenido y el color de fondo. A fin de almacenar este estado local de la IU, debes realizar un seguimiento de si se expande o no un mensaje. Realizaremos el seguimiento de este cambio de estado mediante las funciones remember
y mutableStateOf
.
Las funciones que admiten composición pueden almacenar el estado local en la memoria a través de remember
y detectar cambios en el valor que se pasa a mutableStateOf
. Cuando se actualice el valor, los elementos que admiten composición (y sus elementos secundarios) que usan este estado se volverán a dibujar automáticamente. Esto se denomina recomposición.
Si usas las API de estado de Compose, como remember
y mutableStateOf
, cualquier cambio de estado actualiza la IU automáticamente.
Nota: Debes agregar las siguientes importaciones para usar correctamente Kotlin
sintaxis de propiedad delegada (la palabra clave by
). Se agregan Alt + Intro, o bien Opción + Intro.
por ti.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
// ... import androidx.compose.foundation.clickable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } } } } @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp, ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } } }
Ahora puedes cambiar el fondo del contenido del mensaje en función de isExpanded
cuando hacemos clic en uno. Usarás el modificador clickable
a fin de controlar eventos de clic en el elemento componible. En lugar de activar o desactivar el color de fondo de Surface
, lo animarás modificando gradualmente su valor de MaterialTheme.colorScheme.surface
a MaterialTheme.colorScheme.primary
, y viceversa. Para ello, usarás la función animateColorAsState
. Por último, animarás el tamaño del contenedor de mensajes con facilidad usando el modificador animateContentSize
, como se muestra a continuación:
// ... import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // surfaceColor will be updated gradually from one color to the other val surfaceColor by animateColorAsState( if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface, ) // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp, // surfaceColor color will be changing gradually from primary to surface color = surfaceColor, // animateContentSize will change the Surface size gradually modifier = Modifier.animateContentSize().padding(1.dp) ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } } }
// ... import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @Composable fun Conversation(messages: List<Message>) { LazyColumn { items(messages) { message -> MessageCard(message) } } } @Preview @Composable fun PreviewConversation() { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } }
// ... import androidx.compose.foundation.clickable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } } } } @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp, ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } } }
// ... import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // surfaceColor will be updated gradually from one color to the other val surfaceColor by animateColorAsState( if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface, ) // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp, // surfaceColor color will be changing gradually from primary to surface color = surfaceColor, // animateContentSize will change the Surface size gradually modifier = Modifier.animateContentSize().padding(1.dp) ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } } }
Próximos pasos
¡Felicitaciones! Completaste el instructivo de Compose. Creaste una pantalla de chat sencilla que muestra de manera eficiente una lista de mensajes expandible y animada, y que contiene una imagen y textos diseñados con los principios de Material Design, un tema oscuro y vistas previas (todo en menos de 100 líneas de código).
Esto es lo que aprendiste hasta ahora:
- Cómo definir funciones de componibilidad
- Agregar distintos elementos al elemento componible
- Estructurar un componente de IU con elementos de diseño que admiten composición
- Ampliar elementos componibles con modificadores
- Crear una lista eficiente
- Hacer un seguimiento del estado y modificarlo
- Agregar interacción del usuario a un elemento componible
- Agregar animaciones cuando se expanden mensajes
Si deseas ver en más detalle algunos de estos pasos, explora los siguientes recursos.