Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.
Instructivo

Aspectos básicos 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 crearás directamente los widgets de la IU. 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
Nota: Actualmente, Jetpack Compose se encuentra en la Vista previa para desarrolladores. Aún no se completó la superficie de la API, por lo que no se debe usar en apps de producción.
Vista previa completa

Lección 1: Funciones que se pueden componer

Jetpack Compose se basa en funciones que se pueden componer. Estas funciones te permiten definir la IU de tu app de forma programática describiendo su forma y dependencias de datos, en lugar de enfocarte en el proceso de construcción de la IU. Para crear una función que se pueda componer, agrega la anotación @Composable al nombre de la función.

Agrega un elemento de texto

Para comenzar, sigue las instrucciones de configuración de Jetpack Compose 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, borra las funciones "Saludo" y "Vista previa predeterminada" y el bloque setContent de MainActivity, dejando la actividad en blanco. Compila y ejecuta la app en blanco.

Ahora, agrega un elemento de texto a tu actividad en blanco. Para ello, define un bloque de contenido y llama a la función Text().

El bloque setContent define el diseño de la actividad. En lugar de definir los contenidos del diseño con un archivo en formato XML, llamamos a funciones que se pueden componer. Jetpack Compose usa un complemento de compilador de Kotlin personalizado para transformar estas funciones que se pueden componer en elementos de la IU de la app. Por ejemplo, la función Text() está definida por la biblioteca de la IU de Compose; llama a esa función para declarar un elemento de texto en tu app.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostrar vista previa
ocultar vista previa

Define una función que se puede componer

Las funciones que se pueden componer solo se pueden llamar desde el alcance de otras funciones del mismo tipo. A fin de que una función se pueda componer, debes agregar la anotación @Composable. Para probarlo, define una función Greeting() a la que se le da un nombre que usa para configurar el elemento de texto.

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}
@Composable
fun Greeting(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 compilación de Canary actual de Android Studio te permite obtener una vista previa de tus funciones que se pueden componer dentro del IDE, en lugar de descargar la app a un emulador o dispositivo Android. La restricción principal es que la función que se puede componer no debe tomar ningún parámetro. Por este motivo, no puedes obtener una vista previa de la función Greeting() directamente. En su lugar, crea una segunda función llamada PreviewGreeting(), que llama al objeto Greeting() con un parámetro adecuado. Agrega la anotación @Preview antes del elemento @Composable.

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

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("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 previewGreeting(), pero Android Studio agrega una ventana de vista previa. Esta ventana muestra una vista previa de los elementos de IU creados por la función que se puede componer marcada con la anotación @Preview. Para actualizar las vistas previas en cualquier momento, haz clic en el botón 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 se pueden componer
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostrar vista previa
ocultar vista previa
class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}
@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}
  
mostrar vista previa
ocultar vista previa
Figura 1: Cómo usar Android Studio para obtener una vista previa de tus funciones que se pueden componer

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 se pueden componer desde otras funciones del mismo tipo.

Comienza con algo de texto

Regresa a tu actividad y reemplaza la función Greeting() con una nueva función NewsStory(). Durante el resto del instructivo, modificarás esa función NewsStory(), pero ya no tocarás el código Activity.

La práctica recomendada es crear funciones de vista previa separadas a las que no llama la app, ya que tener funciones exclusivas de vista previa mejora el rendimiento y facilita la configuración de varias vistas previas más adelante. Por lo tanto, crea una función de vista previa predeterminada que solo se encargue de llamar a la función NewsStory(). La vista previa mostrará todos los cambios que realices al objeto NewsStory() durante este instructivo.

Este código crea tres elementos de texto dentro de la vista de contenido. Sin embargo, como no proporcionamos ninguna información sobre cómo organizarlos, los tres elementos de texto se dibujan uno encima del otro, lo que hace que el texto sea ilegible.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
mostrar vista previa
ocultar vista previa

Cómo usar una columna

La función Column te permite apilar elementos de forma vertical. Agrega el objeto Column a la función NewsStory().

La configuración predeterminada apila todos los elementos secundarios directamente, uno tras otro, sin espacios. La columna misma se coloca en la esquina superior izquierda de la vista de contenido.

@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa

Agrega la configuración de estilo a la columna

Al pasar parámetros a la llamada Column, puedes configurar el tamaño y la posición de la columna, así como la forma en que se organizan sus elementos secundarios.

La configuración tiene este significado:

  • modifier: Te permite configurar el diseño. En este caso, aplica un elemento modificador Modifier.padding, que inserta la columna desde la vista circundante.
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa

Agrega una imagen

Queremos agregar un gráfico sobre el texto. Usa Resource Manager para agregar esta foto a los recursos de elementos de diseño de tu app, con el nombre header.

Ahora, modifica tu función NewsStory(). Agregarás una llamada al objeto Image() para colocar el gráfico en el elemento Column. La imagen no tendrá la proporción correcta, pero no te preocupes porque lo solucionarás en el siguiente paso.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa

El gráfico se agrega a tu diseño, pero aún no tiene el tamaño adecuado. Para asignar un estilo al gráfico, pasa un Modifier de tamaño a la llamada al objeto Image().

  • preferredHeightIn(maxHeight = 180.dp): Especifica la altura máxima de la imagen.
  • fillMaxWidth(): Especifica que la imagen debe ser lo suficientemente ancha como para llenar el diseño al que pertenece.

También debes pasar un parámetro contentScale al objeto Image():

  • contentScale = ContentScale.Crop: Especifica que el gráfico debe llenar el ancho de la columna y se debe recortar a la altura adecuada, de ser necesario.
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        HeightSpacer(16.dp)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa

Agrega un objeto Spacer para separar el gráfico de los encabezados.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        HeightSpacer(16.dp)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
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.

Aplica una forma

Uno de los pilares del sistema de Material Design es Shape. Usa la función clip() para redondear las esquinas de la imagen.

El objeto Shape es invisible, pero el gráfico se recorta para ajustarse a Shape, por lo que ahora tiene esquinas ligeramente redondeadas.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa

Aplica estilo al texto

Compose facilita la implementación de los principios de Material Design. Aplica el objeto MaterialTheme a los componentes que creaste.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
mostrar vista previa
ocultar vista previa

Los cambios son sutiles, pero el texto ahora usa el estilo de texto predeterminado del objeto MaterialTheme. Luego, aplica estilos de párrafo específicos a cada elemento de texto.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar vista previa
ocultar vista previa

En este caso, el título del artículo era bastante corto. Pero, a veces, los artículos tienen un título largo, y no queremos que este arruine la apariencia de la app. Cambia el primer elemento de texto.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar vista previa
ocultar vista previa

Configura el elemento de texto para establecer una longitud máxima de 2 líneas. La configuración no tiene efecto si el texto es lo suficientemente corto como para ajustarse a ese límite, pero el texto que se muestra se trunca automáticamente si es demasiado largo.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar vista previa
ocultar vista previa
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar vista previa
ocultar vista previa

Todo listo

¡Muy bien! Aprendiste los conceptos básicos de Compose.

Aprendiste lo siguiente:

  • Cómo definir las funciones que se pueden componer
  • Cómo usar y aplicar estilo a las columnas para mejorar el diseño
  • Cómo diseñar tu app con los principios de Material Design

Si deseas profundizar en algunos de estos pasos, consulta el codelab de los principios básicos de Jetpack Compose.

Ahora que aprendiste los conceptos básicos de Compose, configura tu entorno para que puedas probarlo por tu cuenta.

Recursos

Muestra

Con sus demostraciones de varios componentes, la app de muestra Jetnews ofrece ejemplos de código que funciona para desarrolladores interesados en comprender cómo usar Compose en una app.

Referencia

Explora los documentos para obtener información sobre las API de Jetpack Compose a las que se hace referencia en este instructivo:

Guía

Jetpack Compose se diseñó por completo con Material Design. Los lineamientos de Material Design te ofrecen todo lo que necesitas saber para diseñar tu app, desde el flujo de experiencia del usuario hasta el diseño visual, el movimiento, las fuentes y mucho más.