Sovrapposizione dell'architettura per Jetpack Compose

Questa pagina fornisce una panoramica generale dei livelli di architettura che compongono Jetpack Compose e dei principi fondamentali alla base di questo design.

Jetpack Compose non è un singolo progetto monolitico; viene creato da un numero di moduli assemblati per formare uno stack completo. Comprendere i diversi moduli che compongono Jetpack Compose ti consente di:

  • Utilizza il livello di astrazione appropriato per creare l'app o la libreria
  • Scopri quando puoi "scendere" a un livello inferiore per avere un maggiore controllo o personalizzazione
  • Riduci al minimo le dipendenze

Livelli

I principali livelli di Jetpack Compose sono:

Figura 1. I principali livelli di Jetpack Compose.

Ogni livello si basa sui livelli inferiori, combinando le funzionalità per creare componenti di livello superiore. Ogni livello si basa sulle API pubbliche dei livelli inferiori per verificare i confini del modulo e consentirti di sostituire qualsiasi livello, se necessario. Esaminiamo questi livelli dal basso verso l'alto.

Tempo di esecuzione
Questo modulo fornisce le nozioni di base del runtime di Compose, ad esempio remember, mutableStateOf, l'annotazione @Composable e SideEffect. Potresti prendere in considerazione la possibilità di creare direttamente su questo livello se hai bisogno solo delle funzionalità di gestione dell'albero di Compose, non della sua UI.
Interfaccia utente
Il livello UI è costituito da più moduli ( ui-text, ui-graphics, ui-tooling e così via). Questi moduli implementano le nozioni di base del toolkit UI, ad esempio LayoutNode, Modifier, gestori di input, layout personalizzati e disegno. Ti consigliamo di utilizzare questo livello se hai bisogno solo dei concetti fondamentali di un kit di strumenti per l'interfaccia utente.
Foundation
Questo modulo fornisce componenti di base indipendenti dal sistema di progettazione per l'interfaccia utente di Compose, come Row e Column, LazyColumn, il riconoscimento di determinati gesti e così via. Ti consigliamo di utilizzare il livello di base per creare il tuo sistema di progettazione.
Materiale
Questo modulo fornisce un'implementazione del sistema Material Design per l'interfaccia utente di Compose, offrendo un sistema di temi, componenti stilizzati, indicazioni di effetto ripple e icone. Basati su questo livello quando utilizzi Material Design nella tua app.

Principi di progettazione

Un principio guida di Jetpack Compose è fornire funzionalità piccole e mirate che possono essere assemblate (o composte) insieme, anziché pochi componenti monolitici. Questo approccio presenta una serie di vantaggi.

Controllo

I componenti di livello superiore tendono a fare di più per te, ma limitano il controllo diretto che hai. Se hai bisogno di un maggiore controllo, puoi "abbassarti" per utilizzare un componente di livello inferiore.

Ad esempio, se vuoi animare il colore di un componente, puoi utilizzare l'API animateColorAsState:

val color = animateColorAsState(if (condition) Color.Green else Color.Red)

Tuttavia, se avevi bisogno che il componente fosse sempre grigio all'avvio, non puoi farlo con questa API. In alternativa, puoi utilizzare l'API di livello inferiore Animatable:

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

L'API animateColorAsState di livello superiore si basa sull'API Animatable di livello inferiore. L'utilizzo dell'API di livello inferiore è più complesso, ma offre un maggiore controllo. Scegli il livello di astrazione più adatto alle tue esigenze.

Personalizzazione

Assemblare componenti di livello superiore da elementi di base più piccoli semplifica notevolmente la personalizzazione dei componenti, se necessario. Ad esempio, considera l'implementazione di Button fornita dal livello Material:

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

Un Button è composto da 4 componenti:

  1. Un materiale Surface che fornisce lo sfondo, la forma, la gestione dei clic e così via.

  2. Un CompositionLocalProvider che modifica l'alpha dei contenuti quando il pulsante è attivato o disattivato

  3. Un ProvideTextStyle imposta lo stile di testo predefinito da utilizzare

  4. Un Row fornisce il criterio di layout predefinito per i contenuti del pulsante

Abbiamo omesso alcuni parametri e commenti per rendere la struttura più chiara, ma l'intero componente è composto da circa 40 righe di codice perché assembla semplicemente questi 4 componenti per implementare il pulsante. Componenti come Button hanno un'opinione su quali parametri esporre, bilanciando l'attivazione di personalizzazioni comuni con un'esplosione di parametri che può rendere un componente più difficile da utilizzare. I componenti Material, ad esempio, offrono personalizzazioni specificate nel sistema Material Design, semplificando l'applicazione dei suoi principi.

Tuttavia, se vuoi apportare una personalizzazione oltre i parametri di un componente, puoi "scendere" di un livello e eseguire il fork di un componente. Ad esempio, Material Design specifica che i pulsanti devono avere uno sfondo a tinta unita. Se hai bisogno di uno sfondo sfumato, questa opzione non è supportata dai parametri Button. In questo caso, puoi utilizzare l'implementazione di Material Button come riferimento e creare il tuo componente:

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

L'implementazione riportata sopra continua a utilizzare i componenti del livello Material, come i concetti di Material relativi all'alpha dei contenuti correnti e allo stile di testo corrente. Tuttavia, sostituisce il materiale Surface con un Row e lo stilizza per ottenere l'aspetto desiderato.

Se non vuoi utilizzare i concetti di Material, ad esempio se stai creando il tuo sistema di design personalizzato, puoi utilizzare solo i componenti del livello di base:

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

Jetpack Compose riserva i nomi più semplici per i componenti di primo livello. Ad esempio, androidx.compose.material.Text è basato su androidx.compose.foundation.text.BasicText. In questo modo, puoi fornire la tua implementazione con il nome più facilmente rilevabile se vuoi sostituire i livelli superiori.

Scegliere l'astrazione giusta

La filosofia di Compose, che consiste nel creare componenti riutilizzabili e a più livelli, significa che non devi sempre utilizzare gli elementi di base di livello inferiore. Molti componenti di livello superiore non solo offrono più funzionalità, ma spesso implementano best practice come il supporto dell'accessibilità.

Ad esempio, se vuoi aggiungere il supporto dei gesti al tuo componente personalizzato, puoi crearlo da zero utilizzando Modifier.pointerInput, ma esistono altri componenti di livello superiore basati su questo che potrebbero offrire un punto di partenza migliore, ad esempio Modifier.draggable, Modifier.scrollable o Modifier.swipeable.

Come regola generale, preferisci creare il componente di livello più alto che offre la funzionalità di cui hai bisogno per usufruire delle best practice incluse.

Scopri di più

Consulta l'esempio Jetsnack per un esempio di creazione di un sistema di design personalizzato.