Sebbene Material sia il nostro sistema di progettazione consigliato e Jetpack Compose fornisca un'implementazione di Material, non hai l'obbligo di utilizzarlo. Material è basato interamente su API pubbliche, quindi è possibile creare il tuo sistema di progettazione nello stesso modo.
Puoi adottare diversi approcci:
- Estendi
MaterialThemecon valori di tematizzazione aggiuntivi. - Sostituisci uno o più sistemi Material (
ColorScheme,TypographyoShapes) con implementazioni personalizzate, mantenendo gli altri. - Implementa un sistema di progettazione completamente personalizzato per sostituire
MaterialTheme.
Potresti anche voler continuare a utilizzare i componenti Material con un sistema di progettazione personalizzato. È possibile farlo, ma ci sono alcuni aspetti da tenere presente in base all'approccio che hai adottato.
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.
Estendi i temi di Material
Compose Material modella da vicino i temi di Material per rendere semplice e sicuro seguire le linee guida di Material. Tuttavia, è possibile estendere i set di colori, tipografia e forme con valori aggiuntivi. L'approccio più semplice consiste nell'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)
In questo modo, viene garantita la coerenza con le API di utilizzo di MaterialTheme. Un esempio di questo tipo definito da Compose è surfaceColorAtElevation, che determina il colore della superficie da utilizzare in base all'elevazione.
Un altro approccio consiste nel definire un tema esteso che "unisce" MaterialTheme e i suoi valori.
Supponiamo che tu voglia aggiungere altri due colori, caution e onCaution, un colore giallo utilizzato per le azioni semi-pericolose, mantenendo i colori 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 scenario è simile a quello delle API di utilizzo di MaterialTheme. Supporta anche più temi, in quanto puoi nidificare ExtendedTheme nello stesso modo di MaterialTheme.
Utilizza i componenti Material
Quando estendi i temi di Material, i valori MaterialTheme esistenti vengono mantenuti e i componenti di Material hanno comunque valori predefiniti ragionevoli.
Se vuoi utilizzare valori estesi nei componenti, aggregali nelle tue funzioni componibili, impostando direttamente i valori che vuoi modificare ed esponendo gli altri come parametri per il componibile contenitore:
@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 ) }
Sostituisci quindi gli utilizzi di Button con ExtendedButton, se
appropriato.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Sostituisci i sottosistemi Material
Anziché estendere i temi di Material, potresti voler sostituire uno o più sistemi (ColorScheme, 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 colore:
@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 }
Utilizza i componenti Material
Quando uno o più sistemi di MaterialTheme sono stati sostituiti, l'utilizzo dei componenti Material così come sono potrebbe comportare valori di colore, tipo o forma Material indesiderati.
Se vuoi utilizzare i valori sostitutivi nei componenti, inseriscili nelle tue funzioni componibili, impostando direttamente i valori per il sistema pertinente ed esponendo gli altri come parametri per il componibile contenitore.
@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() } } ) }
Dovrai quindi sostituire gli utilizzi di Button con ReplacementButton, se
appropriato.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Implementa un sistema di progettazione completamente personalizzato
Potresti voler sostituire i temi di Material con un sistema di progettazione completamente personalizzato.
Tieni presente che MaterialTheme fornisce i seguenti sistemi:
ColorScheme,TypographyeShapes: sistemi di temi di MaterialTextSelectionColors: colori utilizzati per la selezione del testo daTexteTextFieldripple, fornito comeLocalIndication: implementazione di Material diIndication
Se vuoi continuare a utilizzare i componenti Material, devi sostituire alcuni di questi sistemi nei tuoi temi personalizzati o gestirli nei tuoi componenti per evitare comportamenti indesiderati.
Tuttavia, i sistemi di progettazione non si limitano ai concetti su cui si basa Material. Puoi modificare i sistemi esistenti e introdurne di completamente 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>), includiamo un sistema di tipi, introduciamo un nuovo sistema di elevazione ed escludiamo 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 }
Utilizza i componenti Material
Se non è presente alcun MaterialTheme, l'utilizzo dei componenti Material così come sono comporterà valori di colore, tipo e forma di Material e un comportamento di indicazione indesiderati.
Se vuoi utilizzare valori personalizzati nei componenti, inseriscili nelle tue funzioni componibili, impostando direttamente i valori per il sistema pertinente ed esponendo gli altri come parametri per il componibile contenitore.
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 impostarli come hardcoded.
@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é eseguirne il wrapping. Per un 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 Compose
- Esegui la migrazione da Material 2 a Material 3 in Compose
- Struttura di un tema in Compose