Applicazione di temi con gli stili

Esistono diversi modi per creare le tue app utilizzando gli stili. La scelta dipende dalla posizione della tua app rispetto all'adozione di Material Design:

  1. Sistema di progettazione completamente personalizzato, che non utilizza Material Design
    • Consiglio: definisci stili dei componenti che utilizzano valori del tema ed esponi i parametri di stile nei componenti del sistema di progettazione.
  2. Utilizzo di Material Design
    • Consiglio: attendi l'adozione di Material per l'integrazione con gli stili. Se possibile, utilizza gli stili sui tuoi componenti.

Il livello Stile

Nel modello Compose tradizionale, la personalizzazione si basa spesso in larga misura sulla sostituzione dei token globali (colori e tipografia) forniti da MaterialTheme o sul wrapping e sulla sostituzione delle proprietà di un componente componibile del sistema di progettazione, ove possibile. A volte, all'interno del livello Material ci sono proprietà che non sono esposte tramite i sottosistemi o i parametri, ma sono valori predefiniti hardcoded nel componente stesso.

Con l'API Styles, è disponibile un nuovo livello di astrazione che funge da ponte tra sottosistemi e componenti: Styles.

incorporato Responsabilità Esempio
Valori del sottosistema Valori denominati val Primary = Color(0xFF34A85E)
Stili atomici Stile che modifica esattamente una proprietà val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic
Stili dei componenti Configurazioni specifiche per i componenti Un pulsante con sfondo principale e spaziatura interna di 16 dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
Componenti L'elemento UI funzionale che utilizza uno stile. Button(style = buttonStyle) { ... }
Diagramma che mostra la creazione di temi con stili con l'introduzione del nuovo livello
Figura 1. Un esempio di componente e di come accede agli stili di un tema.

Stili atomici e monolitici

Con l'API Styles, puoi suddividere uno stile in stili atomici separati. Anziché definire stili complessi e specifici per i componenti come baseButtonStyle, puoi anche creare stili di utilità piccoli e monoscopo. Questi fungono da "atomi".

// Define single-purpose "atomic" styles
val paddingAtomic = Style {
    contentPadding(16.dp)
}
val roundedCornerShapeAtomic = Style {
    shape(RoundedCornerShape(8.dp))
}
val primaryBackgroundAtomic = Style {
    background(Color.Blue)
}
val largeSizeAtomic = Style {
    size(100.dp, 40.dp)
}
val interactiveShadowAtomic = Style {
    hovered {
        animate {
            dropShadow(
                Shadow(
                    offset = DpOffset(
                        0.dp,
                        0.dp
                    ),
                    radius = 2.dp,
                    spread = 0.dp,
                    color = Color.Blue,
                )
            )
        }
    }
}

Composizione con "allora"

Una delle potenti funzionalità della nuova API Styles è l'operatore then, che consente di unire più oggetti Style. In questo modo puoi creare un componente utilizzando classi di utilità atomiche.

Tradizionale (non atomico):

// One large monolithic style
val buttonStyle = Style {
    contentPadding(16.dp)
    shape(RoundedCornerShape(8.dp))
    background(Color.Blue)
}

Refactoring atomico:

// Combine atoms to create the final appearance
val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic

Adottare gli stili nel sistema di progettazione

Quando adotti gli stili all'interno del tuo sistema di progettazione, considera le seguenti opzioni, a seconda della posizione del tuo sistema di progettazione nello spettro.

Sistema di progettazione personalizzato con Stili

Da considerare quando: ti è stata consegnata una guida al brand completa che non si basa su Material Design e non prevedi di utilizzarlo.

Strategia: implementa un sistema di progettazione completamente personalizzato ed esponi gli stili come parte del tema.

Questa opzione è il percorso personalizzato se non utilizzi Material come lingua principale del sistema di progettazione. Hai ignorato completamente MaterialTheme per le definizioni visive e hai già creato il tuo tema personalizzato. Crea un CompanyTheme che funge da contenitore per gli stili.

  • Come funziona: crea un oggetto CompanyTheme che contenga oggetti Style per ogni componente del sistema. I tuoi componenti (wrapper intorno alla logica Material o implementazioni personalizzate di Box o Layout) utilizzano direttamente questi stili ed espongono un parametro Style per i consumatori del tuo sistema di progettazione.
  • Lo strato Stile: gli stili sono la definizione principale del tuo sistema di progettazione. I token sono variabili denominate inserite in questi stili. Ciò consente una personalizzazione approfondita, ad esempio la definizione di animazioni uniche per i cambiamenti di stato (ad esempio, l'animazione di scala e colore alla pressione).

Se stai creando un tema personalizzato senza utilizzare Material e vuoi adottare stili, aggiungi l'elenco di stili al tema. In questo modo puoi accedere agli stili di base da qualsiasi punto del progetto.

  1. Crea una classe Styles che memorizzi i vari stili della tua applicazione e crea i valori predefiniti. Ad esempio, nell'app Jetsnack, la classe è denominata JetsnackStyles:

    object JetsnackStyles{
        val buttonStyle: Style = Style {
            shape(shapes.medium)
            background(colors.brand)
            contentColor(colors.textPrimary)
            contentPaddingVertical(8.dp)
            contentPaddingHorizontal(24.dp)
            textStyle(typography.labelLarge)
            disabled {
                animate {
                    background(colors.brandSecondary)
                }
            }
        }
        val cardStyle: Style = Style {
            shape(shapes.medium)
            background(colors.uiBackground)
            contentColor(colors.textPrimary)
        }
    }

  2. Fornisci Styles come parte del tema generale ed esponi le funzioni di estensione helper su StyleScope per accedere ai sottosistemi:

    @Immutable
    class JetsnackTheme(
        val colors: JetsnackColors = LightJetsnackColors,
        val typography: androidx.compose.material3.Typography = androidx.compose.material3.Typography(),
        val shapes: Shapes = Shapes()
    ) {
        companion object {
            val colors: JetsnackColors
                @Composable @ReadOnlyComposable
                get() = LocalJetsnackTheme.current.colors
    
            val typography: androidx.compose.material3.Typography
                @Composable @ReadOnlyComposable
                get() = LocalJetsnackTheme.current.typography
    
            val shapes: Shapes
                @Composable @ReadOnlyComposable
                get() = LocalJetsnackTheme.current.shapes
    
            val styles: JetsnackStyles = JetsnackStyles
    
            val LocalJetsnackTheme: ProvidableCompositionLocal<JetsnackTheme>
                get() = LocalJetsnackThemeInstance
        }
    }
    
    val StyleScope.colors: JetsnackColors
        get() = LocalJetsnackTheme.currentValue.colors
    
    val StyleScope.typography: androidx.compose.material3.Typography
        get() = LocalJetsnackTheme.currentValue.typography
    
    val StyleScope.shapes: Shapes
        get() = LocalJetsnackTheme.currentValue.shapes
    
    internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() }
    
    @Composable
    fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
        val colors = if (darkTheme) DarkJetsnackColors else LightJetsnackColors
        val theme = JetsnackTheme(colors = colors)
    
        CompositionLocalProvider(
            LocalJetsnackTheme provides theme,
        ) {
            MaterialTheme(
                typography = LocalJetsnackTheme.current.typography,
                shapes = LocalJetsnackTheme.current.shapes,
                content = content,
            )
        }
    }

  3. Accedi a JetsnackStyles all'interno del tuo composable:

    @Composable
    fun CustomButton(modifier: Modifier,
                     style: Style = Style,
                     text: String) {
        val interactionSource = remember { MutableInteractionSource() }
        val styleState = remember(interactionSource) { MutableStyleState(interactionSource) }
    
        // Apply style to top level container in combination with incoming style from parameter.
        Box(modifier = modifier
            .clickable(
                interactionSource = interactionSource,
                indication = null,
                enabled = true,
                role = Role.Button,
                onClick = {
    
                },
            )
            .styleable(styleState, JetsnackTheme.styles.buttonStyle, style)) {
            Text(text)
        }
    }

Oltre all'adozione del tema globale, esistono strategie alternative per incorporare Styles nelle tue app. Puoi sfruttare Styles in linea per siti di chiamate specifici o utilizzare definizioni statiche quando le funzionalità di applicazione di temi complete non sono necessarie. Styles non deve essere scambiato in modo condizionale, a meno che lo stile non sia fondamentalmente diverso. È preferibile accedere ai token dinamici all'interno di una definizione visiva anziché passare da un oggetto di stile all'altro.