Instructivo

Instructivo de Jetpack Compose

Jetpack Compose es un kit de herramientas moderno para crear IU nativas de Android. Jetpack Compose simplifica y acelera el desarrollo de IU en Android con menos código, herramientas potentes y API intuitivas de Kotlin.

En este instructivo, compilarás un componente de IU simple con funciones declarativas. No editarás ningún diseño XML ni usarás el editor de diseño. En cambio, llamarás a las funciones que admiten composición para definir qué elementos quieres, y el compilador de Compose hará el resto.

Vista previa completa
Vista previa completa

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!")
        }
    }
}
  
mostrar vista previa
ocultar vista previa

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!")
}

  
mostrar vista previa
ocultar vista previa

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")
}
  
mostrar vista previa
ocultar vista previa

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.

Vista previa de una función de componibilidad en Android Studio
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!")
        }
    }
}
  
mostrar vista previa
ocultar vista previa
// ...
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!")
}

  
mostrar vista previa
ocultar vista previa
// ...
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
mostrar vista previa
ocultar vista previa
Vista previa de una función de componibilidad en Android Studio

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!")
    )
}

  
mostrar vista previa
ocultar vista previa

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

La función Column te permite organizar los elementos de forma vertical. Agrega el objeto Column a la función MessageCard.
Puedes usar Row para organizar los elementos de manera horizontal y Box para apilarlos.

// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}
mostrar vista previa
ocultar vista previa

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)
        }
  
    }
  
}
  
mostrar vista previa
ocultar vista previa

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)
        }
    }
}
  
mostrar vista previa
ocultar 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!")
    )
}

  
mostrar vista previa
ocultar vista previa
Vista previa de dos elementos de texto que admiten composición superpuestos
// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}
mostrar vista previa
ocultar vista previa
// ...
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)
        }
  
    }
  
}
  
mostrar vista previa
ocultar vista previa
// ...
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)
        }
    }
}
  
mostrar vista previa
ocultar vista previa

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!")
            )
        }
    }
}


  
mostrar vista previa
ocultar vista previa

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)
       }
   }
}

  
mostrar vista previa
ocultar vista previa

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
           )
       }
   }
}

  
mostrar vista previa
ocultar vista previa

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
               )
           }
       }
   }
}

  
mostrar vista previa
ocultar vista previa

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!")
      )
    }
   }
}
  
mostrar vista previa
ocultar vista previa

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!")
      )
    }
   }
}
  
mostrar vista previa
ocultar vista previa
// ...

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!")
            )
        }
    }
}


  
mostrar vista previa
ocultar vista previa
// ...
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)
       }
   }
}

  
mostrar vista previa
ocultar vista previa
// ...

@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
           )
       }
   }
}

  
mostrar vista previa
ocultar vista previa
// ...
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
               )
           }
       }
   }
}

  
mostrar vista previa
ocultar vista previa
// ...
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!")
      )
    }
   }
}
  
mostrar vista previa
ocultar vista previa
Vista previa que muestra elementos componibles de tema claro y oscuro.

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)
    }
}

  
mostrar vista previa
ocultar vista previa

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
                )
            }
        }
    }
}

  
mostrar vista previa
ocultar vista previa

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
                )
            }
        }
    }
}

  
mostrar vista previa
ocultar vista previa
// ...
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)
    }
}

  
mostrar vista previa
ocultar vista previa
// ...
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
                )
            }
        }
    }
}

  
mostrar vista previa
ocultar vista previa
// ...
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
                )
            }
        }
    }
}

  
mostrar vista previa
ocultar vista previa

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.

Próximos pasos

Configuración
Ahora que terminaste el instructivo de Compose, podrás empezar a compilar con Compose.
Ruta de aprendizaje
Consulta nuestra ruta seleccionada de codelabs y videos que te ayudará a obtener información sobre Jetpack Compose y a dominarlo.