Material Design 2 in Compose

Jetpack Compose offre un'implementazione di Material Design, un sistema di progettazione completo per la creazione di interfacce digitali. I componenti di Material Design (pulsanti, schede, interruttori e così via) si basano su Material Design, che è un modo sistematico di personalizzare Material Design in modo che rispecchi meglio il brand del tuo prodotto. Un tema Materiale contiene gli attributi colore, tipografia e forma. Quando personalizzi questi attributi, le modifiche vengono applicate automaticamente nei componenti che utilizzi per creare la tua app.

Jetpack Compose implementa questi concetti con il componente componibile MaterialTheme:

MaterialTheme(
    colors = // ...
    typography = // ...
    shapes = // ...
) {
    // app content
}

Configura i parametri che passi a MaterialTheme per tematizzare l'applicazione.

Due screenshot a contrasto. Il primo screenshot usa lo stile MaterialTheme predefinito,
il secondo lo stile modificato.

Figura 1. Il primo screenshot mostra un'app che non configura MaterialTheme, quindi utilizza lo stile predefinito. Il secondo screenshot mostra un'app che trasmette i parametri a MaterialTheme per personalizzare lo stile.

Colore

I colori sono modellati in Compose con la classe Color, una semplice classe di conservazione dei dati.

val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)

Sebbene tu possa organizzarli come preferisci (come costanti di primo livello, all'interno di un singolo singolo o definito in linea), ti consigliamo vivamente di specificare i colori nel tema e di recuperarli da lì. Questo approccio consente di supportare facilmente tema scuro e temi nidificati.

Esempio della tavolozza dei colori del tema

Figura 2. Il sistema di colori Materiale.

Compose fornisce la classe Colors per modellare il Sistema di colori Material. Colors fornisce le funzioni del builder per creare set di colori chiari o scuri:

private val Yellow200 = Color(0xffffeb46)
private val Blue200 = Color(0xff91a4fc)
// ...

private val DarkColors = darkColors(
    primary = Yellow200,
    secondary = Blue200,
    // ...
)
private val LightColors = lightColors(
    primary = Yellow500,
    primaryVariant = Yellow400,
    secondary = Blue700,
    // ...
)

Una volta definito il Colors, puoi passarlo a un MaterialTheme:

MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors
) {
    // app content
}

Utilizzo dei colori del tema

Puoi recuperare il Colors fornito al componibile MaterialTheme utilizzando MaterialTheme.colors.

Text(
    text = "Hello theming",
    color = MaterialTheme.colors.primary
)

Colore superficie e contenuto

Molti componenti accettano una coppia di colori e colori dei contenuti:

Surface(
    color = MaterialTheme.colors.surface,
    contentColor = contentColorFor(color),
    // ...
) { /* ... */ }

TopAppBar(
    backgroundColor = MaterialTheme.colors.primarySurface,
    contentColor = contentColorFor(backgroundColor),
    // ...
) { /* ... */ }

Questo consente non solo di impostare il colore di un componibile, ma anche di fornire un colore predefinito per i contenuti,ovvero i componibili al suo interno. Molti componibili utilizzano questo colore dei contenuti per impostazione predefinita. Ad esempio, Text basa il proprio colore sul colore dei contenuti dell'elemento principale, mentre Icon lo utilizza per impostare il colore.

Due esempi dello stesso banner, con colori diversi

Figura 3. L'impostazione di colori di sfondo diversi consente di produrre testo e icone di colori diversi.

Il metodo contentColorFor() recupera il colore "on" appropriato per tutti i colori del tema. Ad esempio, se imposti un colore di sfondo primary su Surface, la funzione viene utilizzata per impostare onPrimary come colore del contenuto. Se imposti un colore di sfondo non correlato al tema, devi specificare anche un colore per i contenuti appropriato. Utilizza LocalContentColor per recuperare il colore dei contenuti preferito per lo sfondo corrente, in una determinata posizione nella gerarchia.

Contenuti alpha

Spesso è necessario specificare un livello di enfasi diverso per i contenuti per comunicare l'importanza e fornire una gerarchia visiva. I suggerimenti sulla leggibilità del testo di Material Design consigliano di utilizzare diversi livelli di opacità per trasmettere livelli di importanza differenti.

Jetpack Compose implementa questa operazione tramite LocalContentAlpha. Puoi specificare una versione alpha dei contenuti per una gerarchia fornendo un valore per questa CompositionLocal. I componibili nidificati possono utilizzare questo valore per applicare il trattamento alfa ai propri contenuti. Ad esempio, Text e Icon per impostazione predefinita utilizzano la combinazione di LocalContentColor con aggiustamento per l'uso di LocalContentAlpha. Il materiale specifica alcuni valori alfa standard (high, medium, disabled) modellati dall'oggetto ContentAlpha.

// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
    Text(
        // ...
    )
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
    Icon(
        // ...
    )
    Text(
        // ...
    )
}

Per saperne di più su CompositionLocal, consulta la guida sui dati con ambito locale con la guida ComposeLocal.

Screenshot del titolo di un articolo, che mostra diversi livelli di enfasi del testo

Figura 4. Applicare diversi livelli di enfasi al testo per comunicare visivamente la gerarchia delle informazioni. La prima riga di testo è il titolo e contiene le informazioni più importanti, pertanto utilizza ContentAlpha.high. La seconda riga contiene metadati meno importanti e quindi utilizza ContentAlpha.medium.

Tema scuro

In Compose, implementerai temi chiari e scuri fornendo diversi set di Colors all'elemento componibile MaterialTheme:

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        /*...*/
        content = content
    )
}

In questo esempio, MaterialTheme è racchiuso nella propria funzione componibile, che accetta un parametro che specifica se utilizzare o meno un tema scuro. In questo caso, la funzione recupera il valore predefinito di darkTheme eseguendo una query sull'impostazione del tema del dispositivo.

Puoi utilizzare questo codice per controllare se gli attuali Colors sono chiari o scuri:

val isLightTheme = MaterialTheme.colors.isLight
Icon(
    painterResource(
        id = if (isLightTheme) {
            R.drawable.ic_sun_24
        } else {
            R.drawable.ic_moon_24
        }
    ),
    contentDescription = "Theme"
)

Overlay altitudine

In Material, le superfici in temi scuri ad altitudini più elevate ricevono overlay di elevazione, che illuminano lo sfondo. Più alta è l'elevazione della superficie (più vicina a una sorgente di luce implicita), più la superficie diventa più leggera.

Questi overlay vengono applicati automaticamente dall'elemento componibile Surface quando si utilizzano colori scuri e per qualsiasi altro materiale componibile che utilizza una superficie:

Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }

Screenshot di un'app che mostra i colori leggermente diversi degli elementi
a diversi livelli di altitudine

Figura 5. Le schede e la barra di navigazione in basso utilizzano entrambe il colore surface come sfondo. Poiché le schede e la barra di navigazione in basso si trovano a livelli di altitudine diversi rispetto allo sfondo, hanno colori leggermente diversi: le schede sono più chiare dello sfondo e la navigazione in basso è più chiara delle schede.

Per gli scenari personalizzati che non prevedono un Surface, utilizza LocalElevationOverlay, un CompositionLocal contenente il ElevationOverlay utilizzato dai Surface componenti:

// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
    color, elevation
)

Per disattivare gli overlay di elevazione, fornisci null nel punto desiderato di una gerarchia componibile:

MyTheme {
    CompositionLocalProvider(LocalElevationOverlay provides null) {
        // Content without elevation overlays
    }
}

Accenti di colore limitati

Material consiglia di applicare accenti di colore limitati per i temi scuri preferendo l'uso del colore surface rispetto al colore primary nella maggior parte dei casi. I materiali componibili come TopAppBar e BottomNavigation implementano questo comportamento per impostazione predefinita.

Figura 6. Tema scuro in stile materiale con accenti cromatici limitati. La barra dell'app in alto utilizza il colore principale nel tema chiaro e il colore della superficie nel tema scuro.

Per gli scenari personalizzati, utilizza la proprietà dell'estensione primarySurface:

Surface(
    // Switches between primary in light theme and surface in dark theme
    color = MaterialTheme.colors.primarySurface,
    /*...*/
) { /*...*/ }

Tipografia

Material definisce un sistema di tipi, incoraggiandoti a utilizzare un numero ridotto di stili con nome semantico.

Esempio di diversi caratteri tipografici in stili diversi

Figura 7. Il sistema Material Type.

Compose implementa il sistema dei tipi con le classi Typography, TextStyle e font-related. Il costruttore Typography offre valori predefiniti per ogni stile, così puoi omettere quelli che non vuoi personalizzare:

val raleway = FontFamily(
    Font(R.font.raleway_regular),
    Font(R.font.raleway_medium, FontWeight.W500),
    Font(R.font.raleway_semibold, FontWeight.SemiBold)
)

val myTypography = Typography(
    h1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = myTypography, /*...*/) {
    /*...*/
}

Se vuoi utilizzare lo stesso carattere, specifica defaultFontFamily parameter e ometti fontFamily di qualsiasi elemento TextStyle:

val typography = Typography(defaultFontFamily = raleway)
MaterialTheme(typography = typography, /*...*/) {
    /*...*/
}

Utilizzo degli stili di testo

TextStyle è accessibile tramite MaterialTheme.typography. Recupera i TextStyle in questo modo:

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

Screenshot che mostra una combinazione di caratteri diversi per scopi diversi

Figura 8. Utilizza una selezione di caratteri tipografici e stili per esprimere il tuo brand.

Forma

Il materiale definisce un sistema di forme, che consente di definire le forme per componenti di grandi dimensioni, medie e piccole.

Mostra varie forme di Material Design

Figura 9. Il sistema di forme dei materiali.

Compose implementa il sistema di forme con la classe Shapes, che consente di specificare un CornerBasedShape per ogni categoria di dimensioni:

val shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomEnd = 0.dp,
        bottomStart = 16.dp
    )
)

MaterialTheme(shapes = shapes, /*...*/) {
    /*...*/
}

Molti componenti utilizzano queste forme per impostazione predefinita. Ad esempio, Button, TextField e FloatingActionButton il valore predefinito è Small, AlertDialog il valore predefinito è Medio e ModalDrawer il valore predefinito è Large. Per il mapping completo, consulta il riferimento allo schema delle forme per il mapping completo.

Utilizzo delle forme

Shape è accessibile tramite MaterialTheme.shapes. Recupera i Shape con un codice come questo:

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

Screenshot di un'app che utilizza le forme Material per comunicare lo stato in cui si trova un elemento

Figura 10. Utilizza le forme per esprimere il brand o lo stato.

Stili predefiniti

Non esiste un concetto equivalente in Scrivi degli stili predefiniti di Android View. Puoi fornire una funzionalità simile creando le tue funzioni componibili di "sovraccarico" che aggregano i componenti Material. Ad esempio, per creare uno stile di pulsante, aggrega un pulsante nella tua funzione componibile, impostando direttamente i parametri che vuoi modificare ed esponendo gli altri come parametri all'elemento componibile contenitore.

@Composable
fun MyButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

Overlay di temi

Puoi ottenere l'equivalente degli overlay dei temi delle visualizzazioni Android in Compose, nidificando gli elementi componibili di MaterialTheme. Poiché MaterialTheme imposta per impostazione predefinita colori, tipografia e forme in base al valore del tema corrente, se un tema imposta solo uno di questi parametri, gli altri parametri manterranno i valori predefiniti.

Inoltre, quando esegui la migrazione delle schermate basate sulle visualizzazioni a Compose, presta attenzione agli utilizzi dell'attributo android:theme. È probabile che tu abbia bisogno di un nuovo MaterialTheme in quella parte della struttura ad albero dell'interfaccia utente di Compose.

Nell'esempio di gufo, la schermata dei dettagli utilizza PinkTheme per la maggior parte della schermata, quindi BlueTheme per la sezione correlata. Guarda screenshot e codice di seguito.

Figura 11. Temi nidificati nell'esempio del gufo.

@Composable
fun DetailsScreen(/* ... */) {
    PinkTheme {
        // other content
        RelatedSection()
    }
}

@Composable
fun RelatedSection(/* ... */) {
    BlueTheme {
        // content
    }
}

Stati del componente

I componenti materiali con cui è possibile interagire (clic, pulsante e così via) possono trovarsi in stati visivi diversi. Gli stati includono attivato, disattivato, premuto e così via.

I componibili hanno spesso un parametro enabled. Se il criterio viene impostato su false, l'interazione non viene consentita e vengono modificate proprietà come colore ed elevazione per trasmettere visivamente lo stato del componente.

Figura 12. Pulsante con enabled = true (sinistra) e enabled = false (destra).

Nella maggior parte dei casi puoi utilizzare i valori predefiniti per valori come colore e elevazione. Per configurare valori utilizzati in stati diversi, sono disponibili classi e funzioni di convenienza. Vedi l'esempio di pulsante di seguito:

Button(
    onClick = { /* ... */ },
    enabled = true,
    // Custom colors for different states
    colors = ButtonDefaults.buttonColors(
        backgroundColor = MaterialTheme.colors.secondary,
        disabledBackgroundColor = MaterialTheme.colors.onBackground
            .copy(alpha = 0.2f)
            .compositeOver(MaterialTheme.colors.background)
        // Also contentColor and disabledContentColor
    ),
    // Custom elevation for different states
    elevation = ButtonDefaults.elevation(
        defaultElevation = 8.dp,
        disabledElevation = 2.dp,
        // Also pressedElevation
    )
) { /* ... */ }

Figura 13. Pulsante con enabled = true (sinistra) e enabled = false (destra), con valori di colore e altitudine regolati.

Increspature

I componenti Material utilizzano le onde per indicare l'interazione con i componenti. Se utilizzi MaterialTheme nella gerarchia, un Ripple verrà usato come valore predefinitoIndication all'interno di modificatori come clickable e indication.

Nella maggior parte dei casi puoi usare il valore predefinito Ripple. Se vuoi configurarne l'aspetto, puoi utilizzare RippleTheme per modificare proprietà come colore e alpha.

Puoi estendere RippleTheme e utilizzare le funzioni di utilità defaultRippleColor e defaultRippleAlpha. Puoi quindi fornire il tuo tema a onde personalizzato nella gerarchia utilizzando LocalRippleTheme:

@Composable
fun MyApp() {
    MaterialTheme {
        CompositionLocalProvider(
            LocalRippleTheme provides SecondaryRippleTheme
        ) {
            // App content
        }
    }
}

@Immutable
private object SecondaryRippleTheme : RippleTheme {
    @Composable
    override fun defaultColor() = RippleTheme.defaultRippleColor(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )

    @Composable
    override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )
}

testo_alt

Figura 14. Pulsanti con diversi valori dell'onda forniti tramite RippleTheme.

Scopri di più

Per scoprire di più sui temi dei materiali in Compose, consulta le seguenti risorse aggiuntive.

Codelab

Video