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 se deben tener en cuenta algunos aspectos para el enfoque que has adoptado.
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.
Extensión del 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 quieres agregar dos colores adicionales: caution
y onCaution
, un
amarillo para acciones semipeligrosas, mientras se mantienen las
Colores de Material existentes:
@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 = { /* ... */ }) { /* ... */ } } }
Cómo reemplazar 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 y, al mismo tiempo, mantener el color sistema:
@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 quieres usar valores de reemplazo en componentes, únelos en tu propia funciones de componibilidad, que establezcan directamente los valores para el sistema relevante exponiendo 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 reemplazar los Temas de Material por un sistema de diseño completamente 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 }
Cómo usar 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 tu propio elemento componible. funciones, establecer directamente los valores para el sistema relevante y exponer otros como parámetros del elemento componible que los contiene.
Te recomendamos que accedas a los valores que establezcas desde tu tema personalizado.
Como alternativa, si tu tema no proporciona Color
, TextStyle
, Shape
ni
de 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 introdujiste nuevos tipos de clases, como List<Color>
para representar
con gradientes, quizás sea mejor implementar componentes desde cero
de envolverlos. Por ejemplo, echa un vistazo a
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