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 su lugar, llamarás a las funciones de Jetpack Compose para decir qué elementos deseas, 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 comenzar, descarga la versión más reciente de Android Studio Arctic Fox y crea una app con la plantilla Actividad de Compose vacía. La plantilla predeterminada ya contiene algunos elementos de Compose, pero vamos a compilarla paso a paso.

Primero, mostraremos el mensaje "Hello World!". Para tal fin, agregaremos un elemento de texto dentro del método onCreate. Para ello, define un bloque de contenido y llama a la función Text(). El bloque de setContent define el diseño de la actividad a la que llamamos 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 Text() definida por la biblioteca de la IU de Compose muestra una etiqueta de texto en la pantalla.

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 que admite composición

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.

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

Android Studio te permite obtener una vista previa de las funciones que admiten composición dentro del IDE, en lugar de instalar la app en un emulador o dispositivo Android. La función que admite composición debe proporcionar valores predeterminados para cualquier parámetro. Por este motivo, no puedes obtener una vista previa de la función MessageCard() directamente. En su lugar, crearemos una segunda función denominada PreviewMessageCard(), que llamará al objeto MessageCard() con un parámetro adecuado. Agrega la anotación @Preview antes del elemento @Composable.

@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. 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.

Figura 1: Cómo usar Android Studio para obtener una vista previa de tus funciones que admiten composición
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostrar vista previa
ocultar vista previa
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
@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
mostrar vista previa
ocultar vista previa
Figura 1: Cómo usar Android Studio para obtener una vista previa de tus funciones que admiten composición

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, compilamos nuestra primera función que admite composición y obtuvimos una vista previa. Para descubrir otras capacidades de Jetpack Compose, vamos a compilar una pantalla de mensajes simple que contiene una lista de mensajes expandible con algunas animaciones.
En primer lugar, mejoraremos el contenido de los mensajes mostrando su autor y su contenido. Primero, debemos cambiar el parámetro que admite composición para que acepte un objeto Message en lugar de una String; luego, debemos agregar otro Text que admita composición dentro del objeto MessageCard que admite composición. También asegúrate de actualizar la vista previa de la siguiente manera:

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("Colleague", "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 proporcionamos 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.

Cómo usar 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 horizontalmente y Box para apilarlos.

@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 nuestra tarjeta de mensajes, agregaremos 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:

@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

Nuestro 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. A continuación, usemos algunos para mejorar el diseño:

@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("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

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

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

Nuestros mensajes ya tienen un diseño, pero aún no se ven tan bien.

Jetpack Compose ofrece una implementación de Material Design con elementos de la IU listos para usar. Mejoraremos el aspecto de nuestro elemento componible MessageCard con los estilos de Material Design.

Para comenzar, uniremos la función MessageCard con el tema de Material que creaste en tu proyecto, en este caso, ComposeTutorialTheme. Hazlo tanto en @Preview como en la función setContent.

Material Design se basa en tres pilares: el color, la tipografía y la forma. Vamos a agregarlos uno por uno.

Nota: La actividad de Compose vacía genera un tema predeterminado para el proyecto que te permite personalizar MaterialTheme. Si le asignaste otro nombre a tu proyecto que no sea ComposeTutorial, puedes encontrar el tema que personalizaste en el paquete ui.theme.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                MessageCard(Message("Android", "Jetpack Compose"))
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        MessageCard(
            msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!")
        )
    }
}

  
mostrar vista previa
ocultar vista previa

Color

Editar el estilo con los colores del tema unido es fácil, y puedes usar sus valores en las partes que requieran color.

Ahora apliquemos estilo al título y agreguemos un borde a la imagen:

@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.colors.secondary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant
           )

           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 el objeto MaterialTheme; solo agrégalos a los elementos de texto componibles.

@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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.body2
           )
       }
   }
}

  
mostrar vista previa
ocultar vista previa

Forma

Podemos agregar los toques finales con Shape. También agregamos padding al mensaje a fin de mejorar el diseño.

@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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.body2
               )
           }
       }
   }
}

  
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, 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.

Agreguemos una anotación de vista previa nueva y habilitemos el modo nocturno.

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
       MessageCard(
           msg = Message("Colleague", "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, creamos 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.

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
       MessageCard(
           msg = Message("Colleague", "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 {
                MessageCard(Message("Android", "Jetpack Compose"))
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        MessageCard(
            msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!")
        )
    }
}

  
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.colors.secondary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant
           )

           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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.body2
           )
       }
   }
}

  
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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.body2
               )
           }
       }
   }
}

  
mostrar vista previa
ocultar vista previa
@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
       MessageCard(
           msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
       )
   }
}
  
mostrar vista previa
ocultar vista previa

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, así que cambiemos nuestra conversación para agregar más mensajes. Debemos crear una función Conversation que muestre varios mensajes. Para este caso de uso, podemos usar 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. Al mismo tiempo, evitan la complejidad de RecyclerView con diseños XML.

En este fragmento de código, puedes ver que LazyColumn tiene un elemento secundario de 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. Importa este conjunto de datos de muestra a tu proyecto a fin de procesar la conversación con rapidez de la siguiente manera:

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

Nuestra conversación es cada vez más interesante. Es hora de jugar con las animaciones. Agregaremos 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, debemos 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 componibles (y sus elementos secundarios) que usan este estado se volverán a dibujar automáticamente. Esto se conoce como recomposición.

Si usas las API de estado de Compose, como remember y mutableStateOf, cualquier cambio de estado actualiza la IU automáticamente:

Nota: Deberás agregar las siguientes importaciones para usar "by" de forma correcta. Puedes agregarlas presionando Alt + Intro.

import androidx.compose.runtime.getValue 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.colors.secondaryVariant, 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.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
mostrar vista previa
ocultar vista previa

Ahora podemos cambiar el fondo del contenido del mensaje en función de isExpanded cuando hacemos clic en uno. Usaremos 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 animaremos modificando gradualmente su valor de MaterialTheme.colors.surface a MaterialTheme.colors.primary, y viceversa. Para ello, usaremos la función animateColorAsState. Por último, animaremos el tamaño del contenedor de mensajes con facilidad usando el modificador animateContentSize, como se muestra a continuación:

@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.colors.secondaryVariant, 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: Color by animateColorAsState(
            if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
mostrar vista previa
ocultar vista previa
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
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.colors.secondaryVariant, 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.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
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.colors.secondaryVariant, 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: Color by animateColorAsState(
            if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
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 las funciones que admiten composición
  • 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 con más detalle algunos de estos pasos, explora los siguientes recursos.

Continúa tu aprendizaje

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.