1. Antes de comenzar
En este codelab, aprenderás a crear una lista desplazable en tu app con Jetpack Compose.
Trabajarás con la app de Affirmations, que muestra una lista de afirmaciones vinculadas con imágenes hermosas para aportar optimismo a tu día.
Los datos ya están allí, lo único que debes hacer es tomarlos y mostrarlos en la IU.
Requisitos previos
- Conocimiento sobre listas de Kotlin
- Experiencia en el diseño de diseños con Jetpack Compose
- Experiencia en la ejecución de apps en un dispositivo o emulador
Qué aprenderás
- Cómo crear una tarjeta de Material Design con Jetpack Compose
- Cómo crear una lista desplazable con Jetpack Compose
Qué compilarás
- Tomarás una aplicación existente y agregarás una lista desplazable a la IU.
El producto terminado se verá de la siguiente manera:

Requisitos
- Una computadora con acceso a Internet, un navegador web y Android Studio
- Acceso a GitHub
Descarga el código de partida
En Android Studio, abre la carpeta basic-android-kotlin-compose-training-affirmations.
Se espera que la app muestre una pantalla en blanco cuando se compile a partir del código de rama starter.

2. Crea una clase de datos de elemento de lista
Cómo crear una clase de datos para una Affirmation
En las apps para Android, las listas se componen de elementos de lista. Para datos individuales, esto podría ser algo simple, como una cadena o un número entero. Para los elementos de lista que tienen varios datos, como una imagen y texto, necesitarás una clase que contenga todas estas propiedades. Las clases de datos son un tipo de clase que solo contiene propiedades y pueden proporcionar algunos métodos de utilidad para trabajar con esas propiedades.
- Crea un paquete nuevo en com.example.affirmations.

Asígnale el nombre model. El paquete de modelos contendrá el modelo de datos que representará una clase de datos. Esa clase de datos se compone de propiedades que representan la información relevante a lo que será una "afirmación", que constará de un recurso de cadenas y un recurso de imagen. Los paquetes son directorios que contienen clases e incluso otros directorios.

- Crea una clase nueva en el paquete com.example.affirmations.model.

Asígnale el nombre Affirmation a la nueva clase y conviértela en Data Class.

- Cada
Affirmationconsiste en una imagen y una cadena. Crea dos propiedadesvalen la clase de datosAffirmation. Uno debe llamarsestringResourceIdy el otroimageResourceId. Ambos deben ser números enteros.
Affirmation.kt
data class Affirmation(
val stringResourceId: Int,
val imageResourceId: Int
)
- Anota la propiedad
stringResourceIdcon la anotación@StringResy anota elimageResourceIdcon la anotación@DrawableRes.stringResourceIdrepresenta un ID para el texto de afirmación almacenado en un recurso de cadenas.imageResourceIdrepresenta un ID para la imagen de afirmación almacenada en un recurso de elementos de diseño.
Affirmation.kt
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
data class Affirmation(
@StringRes val stringResourceId: Int,
@DrawableRes val imageResourceId: Int
)
- En el paquete com.example.affirmations.data, abre el archivo Datasource.kt y quita los comentarios de las dos sentencias de importación y el contenido de la clase
Datasource.
Datasource.kt
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
class Datasource() {
fun loadAffirmations(): List<Affirmation> {
return listOf<Affirmation>(
Affirmation(R.string.affirmation1, R.drawable.image1),
Affirmation(R.string.affirmation2, R.drawable.image2),
Affirmation(R.string.affirmation3, R.drawable.image3),
Affirmation(R.string.affirmation4, R.drawable.image4),
Affirmation(R.string.affirmation5, R.drawable.image5),
Affirmation(R.string.affirmation6, R.drawable.image6),
Affirmation(R.string.affirmation7, R.drawable.image7),
Affirmation(R.string.affirmation8, R.drawable.image8),
Affirmation(R.string.affirmation9, R.drawable.image9),
Affirmation(R.string.affirmation10, R.drawable.image10))
}
}
3. Cómo agregar una lista a tu app
Crea una tarjeta de elemento de lista
Esta app está diseñada para mostrar una lista de afirmaciones. El primer paso para configurar la IU para mostrar una lista es crear un elemento de lista. Cada elemento de afirmación consta de una imagen y una cadena. Los datos para cada uno de estos elementos vienen con el código de partida, y crearás el componente de IU para mostrar ese elemento.
El elemento consta de un elemento Card componible que contiene un elemento componible Image y uno Text. En Compose, un Card es una superficie que muestra contenido y acciones en un solo contenedor. La tarjeta de Affirmation se verá de la siguiente manera en la vista previa:

La tarjeta muestra una imagen con un poco de texto debajo. Este diseño vertical se puede lograr usando un elemento Column componible unido en uno Card. Puedes probarlo tú mismo o seguir los pasos a continuación para lograrlo.
- Abre el archivo MainActivity.kt.
- Crea un método nuevo debajo del método
AffirmationsApp(), llamadoAffirmationCard(), y anótalo con la anotación@Composable.
MainActivity.kt
@Composable
fun AffirmationsApp() {
}
@Composable
fun AffirmationCard() {
}
- Edita la firma del método para tomar un objeto
Affirmationcomo parámetro. El objetoAffirmationproviene del paquetemodel.
MainActivity.kt
import com.example.affirmations.model.Affirmation
@Composable
fun AffirmationCard(affirmation: Affirmation) {
}
- Agrega un parámetro
modifiera la firma. Establece un valor predeterminado deModifierpara el parámetro.
MainActivity.kt
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
}
- Dentro del método
AffirmationCard, llama al elemento componibleCard. Pasa el parámetromodifier.
MainActivity.kt
import androidx.compose.material3.Card
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
Card(modifier = modifier) {
}
}
- Agrega una función de componibilidad
Columndentro de la funciónCard. Los elementos dentro de un elementoColumncomponible se organizan verticalmente en la IU. Esto te permite colocar una imagen sobre el texto asociado. Por el contrario, un elementoRowcomponible organiza los elementos contenidos de manera horizontal.
MainActivity.kt
import androidx.compose.foundation.layout.Column
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
Card(modifier = modifier) {
Column {
}
}
}
- Agrega un elemento
Imagecomponible dentro del cuerpo de lambda del elementoColumncomponible. Recuerda que un elementoImagecomponible siempre requiere un recurso para mostrarse y unacontentDescription. El recurso debe ser unpainterResourceque se pase al parámetropainter. El métodopainterResourcecargará elementos de diseño vectoriales o formatos de elementos de trama, como PNG. Además, pasa un elementostringResourcepara el parámetrocontentDescription.
MainActivity.kt
import androidx.compose.foundation.Image
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
Card(modifier = modifier) {
Column {
Image(
painter = painterResource(affirmation.imageResourceId),
contentDescription = stringResource(affirmation.stringResourceId),
)
}
}
}
- Además de los parámetros
painterycontentDescription, pasa unamodifiery unacontentScale. UnacontentScaledetermina cómo se debe escalar y mostrar la imagen. El objetoModifierdebe tener establecido el atributofillMaxWidthy una altura de194.dp. ElcontentScaledebe serContentScale.Crop.
MainActivity.kt
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.ui.unit.dp
import androidx.compose.ui.layout.ContentScale
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
Card(modifier = modifier) {
Column {
Image(
painter = painterResource(affirmation.imageResourceId),
contentDescription = stringResource(affirmation.stringResourceId),
modifier = Modifier
.fillMaxWidth()
.height(194.dp),
contentScale = ContentScale.Crop
)
}
}
}
- Dentro de la
Column, crea un elementoTextcomponible después del elemento componibleImage. Pasa un elementostringResourcedeaffirmation.stringResourceIdal parámetrotext, pasa un objetoModifiercon el atributopaddingconfigurado en16.dpy establece un tema de texto pasandoMaterialTheme.typography.headlineSmallal parámetrostyle.
MainActivity.kt
import androidx.compose.material3.Text
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.platform.LocalContext
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
Card(modifier = modifier) {
Column {
Image(
painter = painterResource(affirmation.imageResourceId),
contentDescription = stringResource(affirmation.stringResourceId),
modifier = Modifier
.fillMaxWidth()
.height(194.dp),
contentScale = ContentScale.Crop
)
Text(
text = LocalContext.current.getString(affirmation.stringResourceId),
modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.headlineSmall
)
}
}
}
Obtén una vista previa del elemento componible AffirmationCard
La tarjeta es el núcleo de la IU de la app de Affirmations, y te esforzaste por crearla. Para comprobar que la tarjeta se vea bien, puedes crear un elemento componible para obtener una vista previa sin tener que iniciar toda la app.
- Crea un método privado llamado
AffirmationCardPreview(). Anota el método con@Previewy@Composable.
MainActivity.kt
import androidx.compose.ui.tooling.preview.Preview
@Preview
@Composable
private fun AffirmationCardPreview() {
}
- Dentro del método, llama a la función de componibilidad
AffirmationCardy pásale un nuevo objetoAffirmationcon el recurso de cadenasR.string.affirmation1y el recurso de elementos de diseñoR.drawable.image1que se pasó a su constructor.
MainActivity.kt
@Preview
@Composable
private fun AffirmationCardPreview() {
AffirmationCard(Affirmation(R.string.affirmation1, R.drawable.image1))
}
- Abre la pestaña Split y obtendrás una vista previa de
AffirmationCard. Si es necesario, haz clic en Build & Refresh en el panel Design para mostrar la vista previa.

Cómo crear la lista
El componente del elemento de lista es el componente básico de la lista. Una vez que se crea el elemento de la lista, puedes aprovecharlo para crear el componente de la lista.
- Crea una función llamada
AffirmationList(), anótala con la anotación@Composabley declara unListde objetosAffirmationcomo parámetro en la firma del método.
MainActivity.kt
@Composable
fun AffirmationList(affirmationList: List<Affirmation>) {
}
- Declara un objeto
modifiercomo parámetro en la firma del método con un valor predeterminado deModifier.
MainActivity.kt
@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
}
- En Jetpack Compose, se puede crear una lista desplazable con el elemento componible
LazyColumn. La diferencia entre un elementoLazyColumny un objetoColumnes que debe usarse un objetoColumncuando tienes una pequeña cantidad de elementos para mostrar, ya que Compose los carga todos a la vez. UnColumnsolo puede contener una cantidad predefinida o fija de elementos componibles. Un elementoLazyColumnpuede agregar contenido a pedido, lo que lo hace útil para listas largas y, en especial, cuando se desconoce la longitud de la lista. UnLazyColumntambién proporciona desplazamiento de manera predeterminada, sin código adicional. Declara un elementoLazyColumncomponible dentro de la funciónAffirmationList(). Pasa el objetomodifiercomo argumento aLazyColumn.
MainActivity.kt
import androidx.compose.foundation.lazy.LazyColumn
@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier) {
}
}
- En el cuerpo de lambda de
LazyColumn, llama al métodoitems()y pasaaffirmationList. El métodoitems()es la manera en la que agregas elementos aLazyColumn. Este método es algo único de ese elemento y no se suele usar para la mayoría de los elementos componibles.
MainActivity.kt
import androidx.compose.foundation.lazy.items
@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier) {
items(affirmationList) {
}
}
}
- Una llamada al método
items()requiere una función lambda. En esa función, especifica un parámetro deaffirmationque represente un elemento de afirmación deaffirmationList.
MainActivity.kt
@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier) {
items(affirmationList) { affirmation ->
}
}
}
- Para cada afirmación de la lista, llama al elemento componible
AffirmationCard(). Pásale laaffirmationy un objetoModifiercon el atributopaddingestablecido en8.dp.
MainActivity.kt
@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier) {
items(affirmationList) { affirmation ->
AffirmationCard(
affirmation = affirmation,
modifier = Modifier.padding(8.dp)
)
}
}
}
Cómo mostrar la lista
- En la función de componibilidad
AffirmationsApp, recupera las direcciones de diseño actuales y guárdalas en una variable. Estas se usarán para configurar el padding más adelante.
MainActivity.kt
import com.example.affirmations.data.Datasource
@Composable
fun AffirmationsApp() {
val layoutDirection = LocalLayoutDirection.current
}
- Ahora, crea un elemento
Surfacecomponible. Ese elemento configurará el padding del elementoAffirmationsListcomponible.
MainActivity.kt
import com.example.affirmations.data.Datasource
@Composable
fun AffirmationsApp() {
val layoutDirection = LocalLayoutDirection.current
Surface() {
}
}
- Pasa un elemento
Modifiera la función de componibilidadSurfaceque completa el ancho y la altura máximos de su elemento superior, establece el padding de la barra de estado y establece el padding inicial y final enlayoutDirection. Este es un ejemplo de cómo traducir un objetoLayoutDirectionen padding:WindowInsets.safeDrawing.asPaddingValues().calculateStartPadding(layoutDirection).
MainActivity.kt
import com.example.affirmations.data.Datasource
@Composable
fun AffirmationsApp() {
val layoutDirection = LocalLayoutDirection.current
Surface(
Modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.padding(
start = WindowInsets.safeDrawing.asPaddingValues()
.calculateStartPadding(layoutDirection),
end = WindowInsets.safeDrawing.asPaddingValues()
.calculateEndPadding(layoutDirection),
),
) {
}
}
- En la expresión lambda de la función de componibilidad
Surface, llama a la función de componibilidadAffirmationListy pasaDataSource().loadAffirmations()al parámetroaffirmationList.
MainActivity.kt
import com.example.affirmations.data.Datasource
@Composable
fun AffirmationsApp() {
val layoutDirection = LocalLayoutDirection.current
Surface(
Modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.padding(
start = WindowInsets.safeDrawing.asPaddingValues()
.calculateStartPadding(layoutDirection),
end = WindowInsets.safeDrawing.asPaddingValues()
.calculateEndPadding(layoutDirection),
),
) {
AffirmationsList(
affirmationList = Datasource().loadAffirmations(),
)
}
}
Ejecuta la app de Affirmations en un dispositivo o emulador, y mira el producto terminado.

4. Obtén el código de la solución
Para descargar el código del codelab terminado, puedes usar estos comandos de git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-affirmations.git $ cd basic-android-kotlin-compose-training-affirmations $ git checkout intermediate
También puedes descargar el repositorio como un archivo ZIP, descomprimirlo y abrirlo en Android Studio.
Si deseas ver el código de la solución, puedes hacerlo en GitHub.
5. Conclusión
Ahora sabes cómo crear tarjetas, elementos de lista y listas desplazables con Jetpack Compose. Ten en cuenta que estas son solo herramientas básicas para crear una lista. Puedes darle rienda suelta a tu creatividad y personalizar los elementos de la lista como quieras.
Resumen
- Usa elementos componibles
Cardpara crear elementos de lista. - Modifica la IU contenida dentro de un elemento
Cardcomponible. - Crea una lista desplazable con el elemento
LazyColumncomponible. - Crea una lista con elementos de una lista personalizada.