Sebbene Material sia il nostro sistema di progettazione consigliato e Jetpack Compose fornisca un'implementazione di Material, non sei obbligato a utilizzarlo. Material è basato interamente su API pubbliche, quindi è possibile creare il tuo sistema di design nello stesso modo.
Puoi adottare diversi approcci:
- Espansione di
MaterialTheme
con valori di temi aggiuntivi - Sostituzione di uno o più sistemi Material (
Colors
,Typography
oShapes
) con implementazioni personalizzate, mantenendo gli altri - Implementazione di un sistema di design completamente personalizzato per sostituire
MaterialTheme
Potresti anche voler continuare a utilizzare i componenti Material con un sistema di design personalizzato. È possibile farlo, ma ci sono alcune cose da tenere a mente per adattarsi all'approccio che hai scelto.
Per scoprire di più sui costrutti e sulle API di livello inferiore utilizzati da MaterialTheme
e dai sistemi di progettazione personalizzati, consulta la guida Anatomia di un tema in Compose.
Estensione del tema Material
Compose Material rispecchia fedelmente Material Theming per semplificare e garantire la sicurezza del tipo di conformità alle linee guida di Material. Tuttavia, è possibile estendere i set di colori, caratteri e forme con valori aggiuntivi.
L'approccio più semplice è aggiungere proprietà di estensione:
// 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)
Ciò garantisce la coerenza con le API di utilizzo di MaterialTheme
. Un esempio di questo valore definito da Compose stesso è surfaceColorAtElevation
, che determina il colore della superficie da utilizzare in base all'elevazione.
Un altro approccio è definire un tema esteso che "avvolge" MaterialTheme
e i suoi valori.
Supponiamo che tu voglia aggiungere altri due colori, caution
e onCaution
, un colore giallo utilizzato per azioni semipericolose, mantenendo i colori di Material esistenti:
@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 }
Questo è simile alle API di utilizzo di MaterialTheme
. Supporta anche più temi, poiché puoi nidificare i ExtendedTheme
nello stesso modo in cui li nidifichi con MaterialTheme
.
Utilizzare i componenti Material
Quando estendi i temi Material, i valori MaterialTheme
esistenti vengono mantenuti
e i componenti Material hanno comunque valori predefiniti ragionevoli.
Se vuoi utilizzare valori estesi nei componenti, inseriscili nelle tue funzioni composable, impostando direttamente i valori che vuoi modificare ed esponendo gli altri come parametri al composable contenente:
@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 ) }
Dovresti quindi sostituire gli utilizzi di Button
con ExtendedButton
, ove opportuno.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Sostituire i sottosistemi di Material
Anziché estendere Material Theming, ti consigliamo di sostituire uno o più sistemi (Colors
, Typography
o Shapes
) con un'implementazione personalizzata, mantenendo gli altri.
Supponiamo che tu voglia sostituire i sistemi di tipo e forma mantenendo il sistema di colori:
@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 }
Utilizzare i componenti Material
Quando uno o più sistemi di MaterialTheme
sono stati sostituiti, l'utilizzo dei componenti Material come sono potrebbe comportare valori indesiderati per colore, tipo o forma del materiale.
Se vuoi utilizzare valori di sostituzione nei componenti, inseriscili nelle tue funzioni composable, impostando direttamente i valori per il sistema pertinente ed esponendo gli altri come parametri al composable contenente.
@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() } } ) }
Dovresti quindi sostituire gli utilizzi di Button
con ReplacementButton
, ove opportuno.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Implementare un sistema di design completamente personalizzato
Ti consigliamo di sostituire Material Theming con un sistema di design completamente personalizzato.
Supponiamo che MaterialTheme
fornisca i seguenti sistemi:
Colors
,Typography
eShapes
: sistemi di temi di MaterialTextSelectionColors
: colori utilizzati per la selezione del testo tramiteText
eTextField
Ripple
eRippleTheme
: implementazione del materiale diIndication
Se vuoi continuare a utilizzare i componenti Material, devi sostituire alcuni di questi sistemi nel tema o nei temi personalizzati oppure gestire i sistemi nei componenti per evitare comportamenti indesiderati.
Tuttavia, i sistemi di design non sono limitati ai concetti su cui si basa Material. Puoi modificare i sistemi esistenti e introdurne di nuovi, con nuove classi e tipi, per rendere altri concetti compatibili con i temi.
Nel codice seguente, modelliamo un sistema di colori personalizzato che include gradienti
(List<Color>
), un sistema di tipi, un nuovo sistema di elevazione
e esclude altri sistemi forniti da 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 }
Utilizzare i componenti Material
Se non è presente MaterialTheme
, l'utilizzo dei componenti Material così come sono comporterà valori e comportamenti indesiderati per colore, tipo e forma del materiale.
Se vuoi utilizzare valori personalizzati nei componenti, inseriscili nelle tue funzioni composable, impostando direttamente i valori per il sistema pertinente ed esponendo gli altri come parametri al composable contenente.
Ti consigliamo di accedere ai valori impostati dal tema personalizzato.
In alternativa, se il tuo tema non fornisce Color
, TextStyle
, Shape
o altri sistemi, puoi codificarli.
@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)
Se hai introdotto nuovi tipi di classi, ad esempio List<Color>
per rappresentare i gradienti, potrebbe essere meglio implementare i componenti da zero anziché incapsularli. Ad esempio, dai un'occhiata a
JetsnackButton
dell'esempio Jetsnack.
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Material Design 3 in Scrittura
- Eseguire la migrazione da Material 2 a Material 3 in Compose
- Struttura di un tema in Scrivi