Sovrapposizione dell'architettura per Jetpack Compose

Questa pagina fornisce una panoramica generale dei livelli architetturali che compongono Jetpack Compose e dei principi fondamentali che informano questo design.

Jetpack Compose non è un singolo progetto monolitico, ma è creato da una serie di moduli assemblati insieme 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
  • Capire quando puoi passare a un livello inferiore per un maggiore controllo o personalizzazione
  • Riduci al minimo le dipendenze

Livelli

I principali livelli di Jetpack Compose sono:

Figura 1. I livelli principali di Jetpack Compose.

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

Runtime
Questo modulo fornisce i principi di base del runtime di Compose, ad esempio remember, mutableStateOf, l'annotazione @Composable e SideEffect. Potresti prendere in considerazione la creazione 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 è composto da più moduli ( ui-text, ui-graphics, ui-tooling, ecc.). Questi moduli implementano i concetti fondamentali del toolkit UI, come LayoutNode, Modifier, gestori di input, layout personalizzati e disegno. Potresti prendere in considerazione la creazione di questo livello se hai bisogno solo dei concetti fondamentali di un toolkit UI.
Fondazione
Questo modulo fornisce componenti di base indipendenti dal sistema di progettazione per l'interfaccia utente Compose, come Row e Column, LazyColumn, il riconoscimento di particolari gesti e così via. Potresti prendere in considerazione la possibilità di basarti sul livello di base per creare il tuo sistema di progettazione.
Materiale
Questo modulo fornisce un'implementazione del sistema Material Design per l'interfaccia utente Compose, fornendo un sistema di temi, componenti con stile, indicazioni ripple e icone. Crea questo livello quando utilizzi Material Design nella tua app.

Principi di progettazione

Un principio guida di Jetpack Compose è fornire piccole funzionalità 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 la quantità di controllo diretto che hai. Se hai bisogno di un maggiore controllo, puoi 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 iniziasse sempre in grigio, non puoi farlo con questa API. In alternativa, puoi utilizzare l'API Animatable di livello inferiore:

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

L'assemblaggio di componenti di livello superiore a partire da blocchi 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 è assemblato 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. A ProvideTextStyle imposta lo stile di testo predefinito da utilizzare

  4. Un Row fornisce le norme di layout predefinite per i contenuti del pulsante

Abbiamo omesso alcuni parametri e commenti per rendere più chiara la struttura, ma l'intero componente è composto solo da circa 40 righe di codice perché assembla semplicemente questi 4 componenti per implementare il pulsante. Componenti come Button sono orientati ai parametri che espongono, bilanciando l'attivazione di personalizzazioni comuni con un'esplosione di parametri che possono rendere un componente più difficile da usare. I componenti Material, ad esempio, offrono personalizzazioni specificate nel sistema Material Design, semplificando il rispetto dei principi di Material Design.

Se, tuttavia, vuoi apportare una personalizzazione oltre i parametri di un componente, puoi "scendere" di un livello e creare una diramazione 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 precedente continua a utilizzare i componenti del livello Material, come i concetti di Material di alfa dei contenuti correnti e lo stile di testo corrente. Tuttavia, sostituisce il materiale Surface con un Row e lo stila per ottenere l'aspetto desiderato.

Se non vuoi utilizzare i concetti di Material, ad esempio se stai creando un sistema di progettazione personalizzato, puoi passare all'utilizzo esclusivo dei 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 livello più alto. Ad esempio, androidx.compose.material.Text è basato su androidx.compose.foundation.text.BasicText. In questo modo è possibile fornire la propria implementazione con il nome più facilmente individuabile se vuoi sostituire i livelli superiori.

Scegliere l'astrazione giusta

La filosofia di Compose di creare componenti a più livelli e riutilizzabili significa che non devi sempre utilizzare i blocchi 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 basarti sul componente di livello più alto che offre la funzionalità di cui hai bisogno per trarre vantaggio dalle best practice che include.

Scopri di più

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