Material è il nostro sistema di progettazione consigliato e Jetpack Compose fornisce un'implementazione di Material, ma non è obbligatorio utilizzarlo. Il Material è basato interamente su API pubbliche, quindi è possibile creare il proprio sistema di progettazione allo stesso modo.
Puoi scegliere tra diversi approcci:
- Estensione di
MaterialTheme
con ulteriori valori di tema - Sostituzione di uno o più sistemi Material (
Colors
,Typography
oShapes
) con implementazioni personalizzate, mantenendo gli altri - Implementazione di un sistema di progettazione completamente personalizzato
per sostituire
MaterialTheme
Puoi anche continuare a utilizzare i componenti Material con un sistema di progettazione personalizzato. È possibile farlo, ma ci sono alcuni aspetti da tenere presenti per adempiere all'approccio che hai adottato.
Per saperne di più sui costrutti di livello inferiore e sulle API utilizzati da MaterialTheme
e sui sistemi di progettazione personalizzati, consulta la guida Struttura di un tema in Compose.
Estensione dei temi materiali
Compose Material modella da vicino Material Theming, per semplificare e rendere sicuro il rispetto delle linee guida di Material. Tuttavia, è possibile estendere gli insiemi di colori, tipografia e forma con valori aggiuntivi.
L'approccio più semplice consiste nell'aggiungere proprietà delle estensioni:
// Use with MaterialTheme.colors.snackbarAction val Colors.snackbarAction: Color get() = if (isLight) 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 coerenza con le API di utilizzo di MaterialTheme
. Un esempio di ciò
definito stesso da Compose è
primarySurface
,
che agisce come proxy tra primary
e surface
in base a
Colors.isLight
.
Un altro approccio è definire un tema esteso che "aggrega" MaterialTheme
e
i suoi valori.
Supponiamo che tu voglia aggiungere altri due colori, tertiary
e onTertiary
, mantenendo i colori Material esistenti:
@Immutable data class ExtendedColors( val tertiary: Color, val onTertiary: Color ) val LocalExtendedColors = staticCompositionLocalOf { ExtendedColors( tertiary = Color.Unspecified, onTertiary = Color.Unspecified ) } @Composable fun ExtendedTheme( /* ... */ content: @Composable () -> Unit ) { val extendedColors = ExtendedColors( tertiary = Color(0xFFA8EFF0), onTertiary = Color(0xFF002021) ) CompositionLocalProvider(LocalExtendedColors provides extendedColors) { MaterialTheme( /* colors = ..., typography = ..., shapes = ... */ content = content ) } } // Use with eg. ExtendedTheme.colors.tertiary object ExtendedTheme { val colors: ExtendedColors @Composable get() = LocalExtendedColors.current }
È simile all'utilizzo di MaterialTheme
API. Supporta inoltre più temi,
poiché puoi nidificare i ExtendedTheme
nello stesso modo di MaterialTheme
.
Utilizzo dei componenti Material
Quando estendi il tema Material, i valori MaterialTheme
esistenti vengono mantenuti e i componenti Material hanno valori predefiniti ragionevoli.
Se vuoi utilizzare valori estesi nei componenti, aggregali nelle tue funzioni componibili, impostando direttamente i valori da modificare ed esponendo altri come parametri nell'elemento componibile contenitore:
@Composable fun ExtendedButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = ExtendedTheme.colors.tertiary, contentColor = ExtendedTheme.colors.onTertiary /* Other colors use values from MaterialTheme */ ), onClick = onClick, modifier = modifier, content = content ) }
Devi quindi sostituire gli utilizzi di Button
con ExtendedButton
, ove appropriato.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Sostituzione dei sistemi di materiali
Invece di estendere i temi materiali, potresti voler sostituire uno o più
sistemi (Colors
, Typography
o Shapes
) con un'implementazione personalizzata,
mantenendo al contempo gli altri.
Supponiamo di voler 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 }
Utilizzo dei componenti Material
Quando uno o più sistemi di MaterialTheme
sono stati sostituiti, l'utilizzo dei componenti Material così come sono, potrebbe comportare valori indesiderati di colore, tipo o forma del materiale.
Se vuoi utilizzare valori sostitutivi nei componenti, aggregali nelle tue funzioni componibili, impostando direttamente i valori per il sistema pertinente ed esponendo altri come parametri nell'elemento 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() } } ) }
Devi quindi sostituire gli utilizzi di Button
con ReplacementButton
, ove appropriato.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Implementazione di un sistema di progettazione completamente personalizzato
Potresti voler sostituire i temi Material con un sistema di progettazione completamente personalizzato.
Considera che MaterialTheme
fornisce i seguenti sistemi:
Colors
,Typography
eShapes
: sistemi di applicazione dei temi MaterialContentAlpha
: livelli di opacità per dare enfasi inText
eIcon
TextSelectionColors
: colori utilizzati per la selezione del testo daText
eTextField
Ripple
eRippleTheme
: implementazione sostanziale diIndication
Se vuoi continuare a utilizzare i componenti Material, dovrai sostituire alcuni di questi sistemi nel tema o nei temi personalizzati oppure gestire i sistemi nei tuoi componenti, per evitare comportamenti indesiderati.
Tuttavia, i sistemi di progettazione non si limitano ai concetti a 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, creiamo un sistema di colori personalizzato che includa gradienti (List<Color>
), sistemi di caratteri, 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 }
Utilizzo dei componenti Material
Quando non è presente MaterialTheme
, l'utilizzo dei componenti Material così come sono, comporta valori indesiderati di colore, tipo e forma di Material e comportamenti delle indicazioni.
Se vuoi utilizzare valori personalizzati nei componenti, aggregali nelle tue funzioni componibili, impostando direttamente i valori per il sistema pertinente ed esponendo gli altri come parametri all'elemento componibile contenitore.
Ti consigliamo di accedere ai valori impostati dal tema personalizzato. In alternativa, se il tuo tema non fornisce i sistemi Color
, TextStyle
, Shape
o altri sistemi, puoi impostare come hardcoded questi elementi.
@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 = ContentAlpha.disabled) ), 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 classe, come List<Color>
per rappresentare i gradienti, potrebbe essere preferibile implementare i componenti da zero anziché includerli a capo. Per un esempio, dai un'occhiata a JetsnackButton
nell'esempio di Jetsnack.
Consigliato per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Material Design 2 in Compose
- Eseguire la migrazione da Material 2 a Material 3 in Compose
- Struttura di un tema in Compose