1. Antes de comenzar
Material Design es un sistema de diseño que crearon y respaldan diseñadores y desarrolladores de Google para crear experiencias digitales de alta calidad para Android, así como otras plataformas web y para dispositivos móviles. Proporciona lineamientos que te permitirán compilar la IU de tu app de manera legible, atractiva y coherente.
En este codelab, aprenderás sobre los Temas de Material, que te permiten usar Material Design en tu app, y te orientaremos para que personalices los colores, la tipografía y las formas. Puedes personalizar todo lo que quieras en tu app. También aprenderás a agregar una barra superior de la aplicación para mostrar el nombre y el ícono de la app.
Requisitos previos
- Conocer el lenguaje Kotlin, incluidas la sintaxis, las funciones y las variables
- Poder compilar diseños en Compose, incluidas filas y columnas con padding
- Ser capaz de crear listas simples en Compose
Qué aprenderás
- Cómo aplicar Temas de Material a una app de Compose
- Cómo agregar una paleta de colores personalizada a tu app
- Cómo agregar fuentes personalizadas a tu app
- Cómo agregar formas personalizadas a los elementos de tu app
- Cómo agregar una barra superior a la app
Qué compilarás
- Compilarás una app atractiva que incorpore las prácticas recomendadas de Material Design.
Requisitos
- La versión más reciente de Android Studio
- Una conexión a Internet para descargar el código de partida y las fuentes
2. Descripción general de la app
En este codelab, crearás Woof, una app que muestra una lista de perros y usa Material Design para crear una experiencia de app atractiva.

En este codelab, te mostraremos lo que se puede lograr con Temas de Material. Usa este codelab para obtener ideas de uso de Temas de Material y mejorar el aspecto de las apps que crees en el futuro.
Paleta de colores
A continuación, se muestran las paletas de colores para los temas claros y oscuros que crearemos.


Esta es la app final con los temas claro y oscuro.
Tema claro | Tema oscuro |
|
|
Tipografía
A continuación, se muestran los estilos de tipo que usarás en la app.

Archivo de tema
El archivo Theme.kt contiene toda la información sobre el tema de la app, que se define por el color, la tipografía y la forma. Es un archivo importante que debes conocer. Dentro del archivo, se encuentra el elemento WoofTheme() componible, que establece los colores, la tipografía y las formas de la app.
@Composable
fun WoofTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColors
else -> LightColors
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
setUpEdgeToEdge(view, darkTheme)
}
}
MaterialTheme(
colorScheme = colorScheme,
shapes = Shapes,
typography = Typography,
content = content
)
}
/**
* Sets up edge-to-edge for the window of this [view]. The system icon colors are set to either
* light or dark depending on whether the [darkTheme] is enabled or not.
*/
private fun setUpEdgeToEdge(view: View, darkTheme: Boolean) {
val window = (view.context as Activity).window
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.Transparent.toArgb()
val navigationBarColor = when {
Build.VERSION.SDK_INT >= 29 -> Color.Transparent.toArgb()
Build.VERSION.SDK_INT >= 26 -> Color(0xFF, 0xFF, 0xFF, 0x63).toArgb()
// Min sdk version for this app is 24, this block is for SDK versions 24 and 25
else -> Color(0x00, 0x00, 0x00, 0x50).toArgb()
}
window.navigationBarColor = navigationBarColor
val controller = WindowCompat.getInsetsController(window, view)
controller.isAppearanceLightStatusBars = !darkTheme
controller.isAppearanceLightNavigationBars = !darkTheme
}
En MainActivity.kt, se agrega WoofTheme() para proporcionar los Temas de Material a toda la app.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WoofTheme {
Surface(
modifier = Modifier.fillMaxSize()
) {
WoofApp()
}
}
}
}
}
Echa un vistazo a WoofPreview(). Se agrega WoofTheme() para proporcionar los Temas de Material que ves en WoofPreview().
@Preview
@Composable
fun WoofPreview() {
WoofTheme(darkTheme = false) {
WoofApp()
}
}
3. Obtén el código de partida
Para comenzar, descarga el código de partida:
Como alternativa, puedes clonar el repositorio de GitHub para el código:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git $ cd basic-android-kotlin-compose-training-woof $ git checkout starter
Puedes explorar el código en el repositorio de GitHub de Woof app.
Explora el código de partida
- Abre el código de partida en Android Studio.
- Abre com.example.woof > data > Dog.kt. Este contiene el objeto
Dog data classque se usará para representar la foto, el nombre, la edad y los pasatiempos del perro. También contiene una lista de perros y la información que usarás como datos en tu app. - Abre res > drawable. Este contiene todos los recursos de imagen que necesitas para el proyecto, incluidos el ícono de la app, los demás íconos y las imágenes de los perros.
- Abre res > values > strings.xml. Allí están las cadenas que usarás en esta app, lo que incluye el nombre de la app, los nombres de los perros, las descripciones y mucho más.
- Abre MainActivity.kt. Este contiene el código para crear una lista sencilla que muestre la foto, el nombre y la edad de un perro.
WoofApp()contiene unaLazyColumnque muestra losDogItem.DogItem()contiene unaRowque muestra una foto y la información del perro.DogIcon()muestra una foto del perro.DogInformation()muestra el nombre y la edad del perro.WoofPreview()te permite obtener una vista previa de la app en el panel Design.
Asegúrate de que el emulador o dispositivo tenga activado el Tema claro
Aunque en este codelab trabajarás con temas claros y oscuros, la mayor parte está en el Tema claro. Antes de comenzar, asegúrate de que tu dispositivo o emulador tenga activado el Tema claro.
Para ver tu app en Tema claro, en el emulador o dispositivo físico, haz lo siguiente:
- Ve a la app de Configuración.
- Busca el Tema oscuro y haz clic en él.
- Si el Tema oscuro está activado, desactívalo.
Ejecuta el código de partida para ver con qué comenzarás: es una lista que muestra perros con sus fotos, nombres y edades. Funciona, pero no se ve bien, así que vamos a corregir eso.

4. Agrega color
Lo primero que modificarás en la app de Woof es el esquema de colores.
Un esquema de colores es la combinación de colores que usa la app. Las distintas combinaciones de colores evocan diferentes estados de ánimo, lo que influye en cómo se sienten las personas cuando usan tu app.
En el sistema Android, el color se representa con un valor hexadecimal (hex). Un código de color hexadecimal comienza con un carácter numeral (#), seguido de seis letras o números que representan los componentes rojo, verde y azul (RGB) de ese color. Las primeras dos letras/números hacen referencia al rojo, las dos siguientes hacen referencia al verde y las dos últimas al azul.

Un color también puede incluir un valor alfa (letras o números), que representa la transparencia del color: #00 es 0% de opacidad (completamente transparente) y #FF es 100% de opacidad (completamente opaco). Cuando se incluye, el valor alfa corresponde a los dos primeros caracteres del código de color hexadecimal que aparece después del numeral (#). Si no se incluye un valor alfa, se supone que es #FF, que es un 100% de opacidad (completamente opaco).
A continuación, se muestran algunos colores de ejemplo y sus valores hexadecimales.

Usa Material Theme Builder para crear un esquema de colores
Para crear un esquema de colores personalizados para nuestra app, usaremos Material Theme Builder.
- Haz clic en este vínculo para ir a Material Theme Builder.
- En el panel izquierdo, verás los colores principales. Haz clic en Primary:

- Se abrirá el selector de color de HCT

- Para crear el esquema de colores que se muestra en las capturas de pantalla de la app, cambiarás el color primario en este selector. En el cuadro de texto, reemplaza el texto actual por #006C4C. Esto hará que el color primario de la app sea verde.

Observa cómo se actualizan las apps en la pantalla para adoptar un esquema de colores verde.

- Desplázate hacia abajo en la página y verás el esquema de colores completo de los temas oscuros y claros que se generaron a partir del color que ingresaste.


Quizás te preguntes qué son todos estos roles y cómo se utilizan. Estos son algunos de los principales:
- Los colores primary (primarios) se usan para los componentes clave de la IU.
- Los colores secondary (secundarios) se usan para los componentes menos destacados de la IU.
- Los colores tertiary (terciarios) se usan para contrastar los acentos que pueden utilizarse para equilibrar los colores primarios y secundarios, o dirigir la atención hacia un elemento, como un campo de entrada.
- Los elementos de color on aparecen arriba de otros colores en la paleta y se aplican principalmente al texto, la iconografía y los trazos. En la paleta de colores, tenemos un color onSurface, que aparece en la parte superior del color surface, y un color onPrimary, que aparece en la parte superior del color primary.
Tener estas ranuras conduce a un sistema de diseño cohesivo, en el que los componentes relacionados se colorean de manera similar.
Suficiente teoría sobre los colores: es hora de agregar esta hermosa paleta de colores a la app.
Agrega una paleta de colores al tema
En la página de Material Theme Builder, puedes hacer clic en el botón Export para descargar un archivo Color.kt y un archivo Theme.kt con el tema personalizado que creaste en Theme Builder.
Esto funcionará para agregar el tema personalizado que creemos a tu app. Sin embargo, como el archivo Theme.kt generado no incluye el código de color dinámico que veremos más adelante en el codelab, copia los archivos.
- Abre el archivo Color.kt y reemplaza el contenido con el siguiente código para copiarlo en el nuevo esquema de colores.
package com.example.woof.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF006C4C)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFF89F8C7)
val md_theme_light_onPrimaryContainer = Color(0xFF002114)
val md_theme_light_secondary = Color(0xFF4D6357)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFCFE9D9)
val md_theme_light_onSecondaryContainer = Color(0xFF092016)
val md_theme_light_tertiary = Color(0xFF3D6373)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFC1E8FB)
val md_theme_light_onTertiaryContainer = Color(0xFF001F29)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFBFDF9)
val md_theme_light_onBackground = Color(0xFF191C1A)
val md_theme_light_surface = Color(0xFFFBFDF9)
val md_theme_light_onSurface = Color(0xFF191C1A)
val md_theme_light_surfaceVariant = Color(0xFFDBE5DD)
val md_theme_light_onSurfaceVariant = Color(0xFF404943)
val md_theme_light_outline = Color(0xFF707973)
val md_theme_light_inverseOnSurface = Color(0xFFEFF1ED)
val md_theme_light_inverseSurface = Color(0xFF2E312F)
val md_theme_light_inversePrimary = Color(0xFF6CDBAC)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF006C4C)
val md_theme_light_outlineVariant = Color(0xFFBFC9C2)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFF6CDBAC)
val md_theme_dark_onPrimary = Color(0xFF003826)
val md_theme_dark_primaryContainer = Color(0xFF005138)
val md_theme_dark_onPrimaryContainer = Color(0xFF89F8C7)
val md_theme_dark_secondary = Color(0xFFB3CCBE)
val md_theme_dark_onSecondary = Color(0xFF1F352A)
val md_theme_dark_secondaryContainer = Color(0xFF354B40)
val md_theme_dark_onSecondaryContainer = Color(0xFFCFE9D9)
val md_theme_dark_tertiary = Color(0xFFA5CCDF)
val md_theme_dark_onTertiary = Color(0xFF073543)
val md_theme_dark_tertiaryContainer = Color(0xFF244C5B)
val md_theme_dark_onTertiaryContainer = Color(0xFFC1E8FB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF191C1A)
val md_theme_dark_onBackground = Color(0xFFE1E3DF)
val md_theme_dark_surface = Color(0xFF191C1A)
val md_theme_dark_onSurface = Color(0xFFE1E3DF)
val md_theme_dark_surfaceVariant = Color(0xFF404943)
val md_theme_dark_onSurfaceVariant = Color(0xFFBFC9C2)
val md_theme_dark_outline = Color(0xFF8A938C)
val md_theme_dark_inverseOnSurface = Color(0xFF191C1A)
val md_theme_dark_inverseSurface = Color(0xFFE1E3DF)
val md_theme_dark_inversePrimary = Color(0xFF006C4C)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF6CDBAC)
val md_theme_dark_outlineVariant = Color(0xFF404943)
val md_theme_dark_scrim = Color(0xFF000000)
- Abre el archivo Theme.kt y reemplaza el contenido con el siguiente código para agregar los nuevos colores al tema.
package com.example.woof.ui.theme
import android.app.Activity
import android.os.Build
import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val LightColors = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim,
)
private val DarkColors = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
@Composable
fun WoofTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColors
else -> LightColors
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
setUpEdgeToEdge(view, darkTheme)
}
}
MaterialTheme(
colorScheme = colorScheme,
shapes = Shapes,
typography = Typography,
content = content
)
}
/**
* Sets up edge-to-edge for the window of this [view]. The system icon colors are set to either
* light or dark depending on whether the [darkTheme] is enabled or not.
*/
private fun setUpEdgeToEdge(view: View, darkTheme: Boolean) {
val window = (view.context as Activity).window
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.Transparent.toArgb()
val navigationBarColor = when {
Build.VERSION.SDK_INT >= 29 -> Color.Transparent.toArgb()
Build.VERSION.SDK_INT >= 26 -> Color(0xFF, 0xFF, 0xFF, 0x63).toArgb()
// Min sdk version for this app is 24, this block is for SDK versions 24 and 25
else -> Color(0x00, 0x00, 0x00, 0x50).toArgb()
}
window.navigationBarColor = navigationBarColor
val controller = WindowCompat.getInsetsController(window, view)
controller.isAppearanceLightStatusBars = !darkTheme
controller.isAppearanceLightNavigationBars = !darkTheme
}
En WoofTheme(), colorScheme val usa una sentencia when.
- Si
dynamicColores "true" y la versión de compilación es S o posterior, comprueba si el dispositivo está endarkThemeo no. - Si está en el tema oscuro, se establecerá
colorSchemecomodynamicDarkColorScheme. - Si no está en el tema oscuro, se establecerá en
dynamicLightColorScheme. - Si la app no usa
dynamicColorScheme, verifica si está endarkTheme. Si es así,colorSchemese configurará comoDarkColors. - Si ninguna de esas condiciones es "true",
colorSchemese establecerá comoLightColors.
El archivo Theme.kt copiado tiene dynamicColor configurado como "false" y los dispositivos con los que estamos trabajando están en modo claro, por lo que colorScheme se establecerá como LightColors.
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColors
else -> LightColors
}
- Vuelve a ejecutar tu app. Observa que la barra de la app cambió automáticamente de color.

Asignación de colores
Los componentes de Material se asignan automáticamente a ranuras de color. Otros componentes clave en la IU, como los botones de acción flotantes, también tienen color primario de manera predeterminada. Esto significa que no necesitas asignar de forma explícita un color a un componente; se asigna automáticamente a una ranura de color cuando configuras el tema de color en tu app. Puedes anular esto si configuras de manera explícita un color en el código. Obtén más información sobre los roles de color aquí.
En esta sección, uniremos la Row que contiene DogIcon() y DogInformation() con una Card para diferenciar los colores del elemento de lista con el fondo.
- En la función de componibilidad
DogItem(), uneRow()conCard().
Card() {
Row(
modifier = modifier
.fillMaxWidth()
.padding(dimensionResource(id = R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
}
}
- Como ahora
Cardes el primer elemento componible secundario enDogItem(), pasa el modificador deDogItem()aCardy actualiza el modificador deRowa una nueva instancia deModifier.
Card(modifier = modifier) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(id = R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
}
}
- Echa un vistazo a
WoofPreview(). Los elementos de lista ahora cambiaron automáticamente de color debido a los elementosCardcomponibles. Los colores se
