Si bien recomendamos el sistema de diseño Material y Jetpack Compose viene con una implementación incluida, no es obligatorio que lo uses. Material está íntegramente compilado sobre APIs públicas, por lo que es posible que crees tu propio sistema de diseño de la misma manera.
Puedes adoptar varios enfoques:
- Extiende
MaterialTheme
con valores de temas adicionales - Reemplaza uno o más sistemas Material (
Colors
,Typography
oShapes
) por implementaciones personalizadas, a la vez que mantienes los otros. - Implementa un sistema de diseño totalmente personalizado para reemplazar
MaterialTheme
.
También puedes seguir usando componentes de Material con un diseño personalizado en un sistema de archivos. Es posible hacerlo, pero debes tener en cuenta algunos aspectos para adaptar el enfoque que elegiste.
Para obtener más información sobre las construcciones de nivel inferior y las APIs que usan MaterialTheme
y los sistemas de diseño personalizado, consulta la guía Anatomía de un tema en Compose.
Cómo extender el tema de Material
Compose Material modela cuidadosamente los temas de Material para que los lineamientos correspondientes sean simples y seguros de seguir. Sin embargo, es posible extender los conjuntos de color, tipografía y formas con de salida.
El enfoque más simple es agregar propiedades de extensión:
// Use with MaterialTheme.colorScheme.snackbarAction val ColorScheme.snackbarAction: Color @Composable get() = if (isSystemInDarkTheme()) Red300 else Red700 // Use with MaterialTheme.typography.textFieldInput val Typography.textFieldInput: TextStyle get() = TextStyle(/* ... */) // Use with MaterialTheme.shapes.card val Shapes.card: Shape get() = RoundedCornerShape(size = 20.dp)
De esta manera, se brinda coherencia con las APIs de uso de MaterialTheme
. Un ejemplo de esto
definido por el mismo Compose
surfaceColorAtElevation
,
que determina el color de superficie que se debe utilizar según la elevación.
Otro enfoque es definir un tema extendido que “envuelve” MaterialTheme
y
sus valores.
Supongamos que deseas agregar dos colores adicionales, caution
y onCaution
, un color amarillo que se usa para acciones semipeligrosas, a la vez que mantienes los colores existentes de Material:
@Immutable data class ExtendedColors( val caution: Color, val onCaution: Color ) val LocalExtendedColors = staticCompositionLocalOf { ExtendedColors( caution = Color.Unspecified, onCaution = Color.Unspecified ) } @Composable fun ExtendedTheme( /* ... */ content: @Composable () -> Unit ) { val extendedColors = ExtendedColors( caution = Color(0xFFFFCC02), onCaution = Color(0xFF2C2D30) ) CompositionLocalProvider(LocalExtendedColors provides extendedColors) { MaterialTheme( /* colors = ..., typography = ..., shapes = ... */ content = content ) } } // Use with eg. ExtendedTheme.colors.caution object ExtendedTheme { val colors: ExtendedColors @Composable get() = LocalExtendedColors.current }
Es similar a las APIs de uso de MaterialTheme
. También admite varios temas, ya que puedes anidar objetos ExtendedTheme
de la misma manera que MaterialTheme
.
Cómo usar componentes de Material
Cuando se extienden los temas de Material, se mantienen los valores MaterialTheme
existentes, y los componentes de Material continúan teniendo valores predeterminados razonables.
Si deseas usar valores extendidos en los componentes, únelos en tu propia funciones de componibilidad, configurar directamente los valores que deseas modificar exponiendo otros como parámetros al elemento componible que los contiene:
@Composable fun ExtendedButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = ExtendedTheme.colors.caution, contentColor = ExtendedTheme.colors.onCaution /* Other colors use values from MaterialTheme */ ), onClick = onClick, modifier = modifier, content = content ) }
Luego, reemplaza los usos de Button
por ExtendedButton
cuando corresponda.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Reemplaza los subsistemas de Material
En lugar de extender los Temas de Material, puedes reemplazar uno o más
sistemas (Colors
, Typography
o Shapes
) con una implementación personalizada,
mientras se mantienen las demás.
Supongamos que deseas reemplazar los sistemas de tipos y formas mientras mantienes el sistema de colores:
@Immutable data class ReplacementTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class ReplacementShapes( val component: Shape, val surface: Shape ) val LocalReplacementTypography = staticCompositionLocalOf { ReplacementTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalReplacementShapes = staticCompositionLocalOf { ReplacementShapes( component = RoundedCornerShape(ZeroCornerSize), surface = RoundedCornerShape(ZeroCornerSize) ) } @Composable fun ReplacementTheme( /* ... */ content: @Composable () -> Unit ) { val replacementTypography = ReplacementTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val replacementShapes = ReplacementShapes( component = RoundedCornerShape(percent = 50), surface = RoundedCornerShape(size = 40.dp) ) CompositionLocalProvider( LocalReplacementTypography provides replacementTypography, LocalReplacementShapes provides replacementShapes ) { MaterialTheme( /* colors = ... */ content = content ) } } // Use with eg. ReplacementTheme.typography.body object ReplacementTheme { val typography: ReplacementTypography @Composable get() = LocalReplacementTypography.current val shapes: ReplacementShapes @Composable get() = LocalReplacementShapes.current }
Cómo usar componentes de Material
Cuando se reemplazan uno o más sistemas de MaterialTheme
, es posible que usar los componentes de Material tal como están genere valores de color, tipo o forma de Material no deseados.
Si deseas usar valores de reemplazo en los componentes, únelos en tus propias funciones de componibilidad, configura directamente los valores para el sistema relevante y expón otros como parámetros al elemento componible que los contiene.
@Composable fun ReplacementButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( shape = ReplacementTheme.shapes.component, onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = ReplacementTheme.typography.body ) { content() } } ) }
Luego, reemplaza los usos de Button
por ReplacementButton
cuando corresponda.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Implementa un sistema de diseño totalmente personalizado
Te recomendamos que reemplaces los temas de Material por un sistema de diseño totalmente personalizado.
Ten en cuenta que MaterialTheme
brinda los siguientes sistemas:
Colors
,Typography
yShapes
: Sistemas de temas de MaterialTextSelectionColors
: Colores que usaText
yTextField
para seleccionar textoRipple
yRippleTheme
: Implementación de Material deIndication
Si deseas continuar usando componentes de Material, deberás reemplazar algunos de estos sistemas en los temas personalizados o controlar los sistemas en los componentes para evitar un comportamiento no deseado.
Sin embargo, los sistemas de diseño no se limitan a los conceptos en los que se basa Material. Puedes modificar los sistemas existentes y agregar sistemas completamente nuevos, con clases y tipos nuevos, para que otros conceptos sean compatibles con los temas.
En el siguiente código, modelamos un sistema de colores personalizado que incluye gradientes (List<Color>
), incluimos un sistema de tipos, agregamos un sistema nuevo de elevación y excluimos otros sistemas que brinda MaterialTheme
:
@Immutable data class CustomColors( val content: Color, val component: Color, val background: List<Color> ) @Immutable data class CustomTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class CustomElevation( val default: Dp, val pressed: Dp ) val LocalCustomColors = staticCompositionLocalOf { CustomColors( content = Color.Unspecified, component = Color.Unspecified, background = emptyList() ) } val LocalCustomTypography = staticCompositionLocalOf { CustomTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalCustomElevation = staticCompositionLocalOf { CustomElevation( default = Dp.Unspecified, pressed = Dp.Unspecified ) } @Composable fun CustomTheme( /* ... */ content: @Composable () -> Unit ) { val customColors = CustomColors( content = Color(0xFFDD0D3C), component = Color(0xFFC20029), background = listOf(Color.White, Color(0xFFF8BBD0)) ) val customTypography = CustomTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val customElevation = CustomElevation( default = 4.dp, pressed = 8.dp ) CompositionLocalProvider( LocalCustomColors provides customColors, LocalCustomTypography provides customTypography, LocalCustomElevation provides customElevation, content = content ) } // Use with eg. CustomTheme.elevation.small object CustomTheme { val colors: CustomColors @Composable get() = LocalCustomColors.current val typography: CustomTypography @Composable get() = LocalCustomTypography.current val elevation: CustomElevation @Composable get() = LocalCustomElevation.current }
Usa componentes de Material
Cuando ningún objeto MaterialTheme
está presente, usar los componentes de Material tal como están generará valores de color, tipo y forma de Material no deseados, así como comportamiento de indicación.
Si deseas usar valores personalizados en los componentes, únelos en tus propias funciones de componibilidad, configura directamente los valores para el sistema relevante y expón otros como parámetros al elemento componible que los contiene.
Te recomendamos que accedas a los valores que establezcas desde el tema personalizado.
Como alternativa, si el tema no brinda Color
, TextStyle
, Shape
ni otros sistemas, puedes codificarlos.
@Composable fun CustomButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = CustomTheme.colors.component, contentColor = CustomTheme.colors.content, disabledContainerColor = CustomTheme.colors.content .copy(alpha = 0.12f) .compositeOver(CustomTheme.colors.component), disabledContentColor = CustomTheme.colors.content .copy(alpha = 0.38f) ), shape = ButtonShape, elevation = ButtonDefaults.elevatedButtonElevation( defaultElevation = CustomTheme.elevation.default, pressedElevation = CustomTheme.elevation.pressed /* disabledElevation = 0.dp */ ), onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = CustomTheme.typography.body ) { content() } } ) } val ButtonShape = RoundedCornerShape(percent = 50)
Si agregaste nuevos tipos de clases, como List<Color>
para representar gradientes, es posible que sea mejor implementar componentes desde cero en lugar de unirlos. Por ejemplo, observa JetsnackButton
del ejemplo de Jetsnack.
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Material Design 3 en Compose
- Cómo migrar de Material 2 a Material 3 en Compose
- Anatomía de un tema en Compose