Nozioni di base sul layout di Scrivi

Jetpack Compose semplifica notevolmente la progettazione e la creazione della UI della tua app. Compose trasforma lo stato in elementi UI tramite:

  1. Composizione di elementi
  2. Layout degli elementi
  3. Disegno di elementi

Compose trasforma lo stato in UI tramite composizione, layout e disegno

Questo documento si concentra sul layout degli elementi, spiegando alcuni dei componenti di base forniti da Compose per aiutarti a disporre gli elementi UI.

Obiettivi dei layout in Compose

L'implementazione del sistema di layout di Jetpack Compose ha due obiettivi principali:

Nozioni di base sulle funzioni componibili

Le funzioni componibili sono l'elemento di base di Compose. Una funzione componibile è una funzione che genera Unit che descrive una parte della UI. La funzione accetta un input e genera ciò che viene mostrato sullo schermo. Per saperne di più sui componibili, consulta la documentazione sul modello mentale di Compose.

Una funzione componibile potrebbe emettere diversi elementi UI. Tuttavia, se non fornisci indicazioni su come devono essere disposti, Compose potrebbe disporre gli elementi in un modo che non ti piace. Ad esempio, questo codice genera due elementi di testo:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Senza indicazioni su come disporli, Compose impila gli elementi di testo uno sopra l'altro, rendendoli illeggibili:

Due elementi di testo disegnati uno sopra l'altro, che rendono il testo illeggibile

Compose fornisce una raccolta di layout pronti all'uso per aiutarti a organizzare gli elementi UI e semplifica la definizione di layout personalizzati più specializzati.

Componenti di layout standard

In molti casi, puoi semplicemente utilizzare gli elementi di layout standard di Compose.

Utilizza Column per posizionare gli elementi verticalmente sullo schermo.

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Due elementi di testo disposti in un layout a colonne, in modo che il testo sia leggibile

Allo stesso modo, utilizza Row per posizionare gli elementi in orizzontale sullo schermo. Sia Column che Row supportano la configurazione dell'allineamento degli elementi che contengono.

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

Mostra un layout più complesso, con un piccolo elemento grafico accanto a una colonna di elementi di testo

Utilizza Box per posizionare gli elementi uno sopra l'altro. Box supporta anche la configurazione di un allineamento specifico degli elementi che contiene.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

Mostra due elementi impilati uno sull'altro

Spesso questi componenti di base sono tutto ciò di cui hai bisogno. Puoi scrivere la tua funzione componibile per combinare questi layout in un layout più elaborato adatto alla tua app.

Confronta tre composabili di layout semplici: colonna, riga e riquadro

Per impostare la posizione degli elementi secondari all'interno di un Row, imposta gli argomenti horizontalArrangement e verticalAlignment. Per un Column, imposta gli argomenti verticalArrangement e horizontalAlignment:

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

Gli elementi sono allineati a destra

Il modello di layout

Nel modello di layout, l'albero della UI viene disposto in un unico passaggio. A ogni nodo viene chiesto prima di misurare se stesso, poi di misurare in modo ricorsivo i nodi secondari, passando i vincoli di dimensione lungo l'albero ai nodi secondari. Quindi, i nodi foglia vengono dimensionati e posizionati, con le dimensioni risolte e le istruzioni di posizionamento trasmesse nuovamente all'albero.

In breve, i genitori misurano prima dei figli, ma vengono dimensionati e posizionati dopo i figli.

Considera la seguente funzione SearchResult.

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

Questa funzione genera il seguente albero della UI.

SearchResult
  Row
    Image
    Column
      Text
      Text

Nell'esempio SearchResult, il layout dell'albero della UI segue questo ordine:

  1. Il nodo principale Row deve essere misurato.
  2. Il nodo principale Row chiede al suo primo elemento secondario, Image, di eseguire la misurazione.
  3. Image è un nodo foglia (ovvero non ha nodi secondari), quindi segnala una dimensione e restituisce le istruzioni di posizionamento.
  4. Il nodo principale Row chiede al suo secondo nodo secondario, Column, di misurare.
  5. Il nodo Column chiede al suo primo elemento secondario Text di misurare.
  6. Il primo nodo Text è un nodo foglia, quindi segnala una dimensione e restituisce le istruzioni di posizionamento.
  7. Il nodo Column chiede al suo secondo nodo figlio Text di misurare.
  8. Il secondo nodo Text è un nodo foglia, quindi segnala una dimensione e restituisce le istruzioni di posizionamento.
  9. Ora che il nodo Column ha misurato, dimensionato e posizionato i suoi elementi secondari, può determinare le proprie dimensioni e il proprio posizionamento.
  10. Ora che il nodo principale Row ha misurato, dimensionato e posizionato i suoi elementi secondari, può determinare le proprie dimensioni e il proprio posizionamento.

Ordine di misurazione, dimensionamento e posizionamento nell'albero della UI dei risultati di ricerca

Prestazioni

Compose ottiene prestazioni elevate misurando gli elementi secondari una sola volta. La misurazione in un unico passaggio è utile per le prestazioni, in quanto consente a Compose di gestire in modo efficiente alberi UI profondi. Se un elemento misurasse il suo elemento secondario due volte e quest'ultimo misurasse ciascuno dei suoi elementi secondari due volte e così via, un singolo tentativo di definire il layout di un'intera UI richiederebbe molto lavoro, rendendo difficile mantenere le prestazioni dell'app.

Se per qualche motivo il tuo layout richiede più misurazioni, Compose offre un sistema speciale, le misurazioni intrinseche. Puoi scoprire di più su questa funzionalità in Misurazioni intrinseche nei layout di Compose.

Poiché la misurazione e il posizionamento sono sottofasi distinte della fase di layout, qualsiasi modifica che influisce solo sul posizionamento degli elementi, non sulla misurazione, può essere eseguita separatamente.

Utilizza i modificatori nei layout

Come descritto in Modificatori di Compose, puoi utilizzare i modificatori per decorare o migliorare i tuoi componibili. I modificatori sono essenziali per personalizzare il layout. Ad esempio, qui concateniamo diversi modificatori per personalizzare il ArtistCard:

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

Un layout ancora più complesso, che utilizza i modificatori per cambiare la disposizione degli elementi grafici e le aree che rispondono all'input dell'utente

Nel codice riportato sopra, nota le diverse funzioni di modifica utilizzate insieme.

  • clickable fa reagire un composable all'input dell'utente e mostra un ripple.
  • padding inserisce uno spazio intorno a un elemento.
  • fillMaxWidth fa sì che l'elemento componibile occupi la larghezza massima assegnata dall'elemento principale.
  • size() specifica la larghezza e l'altezza preferite di un elemento.

Layout scorrevoli

Scopri di più sui layout scorrevoli nella documentazione sui gesti di Compose.

Per gli elenchi e gli elenchi lazy, consulta la documentazione sugli elenchi di Compose.

Layout reattivi

Un layout deve essere progettato tenendo conto dei diversi orientamenti dello schermo e delle dimensioni dei fattori di forma. Compose offre diversi meccanismi pronti all'uso per facilitare l'adattamento dei layout componibili a varie configurazioni dello schermo.

Vincoli

Per conoscere i vincoli provenienti dall'elemento principale e progettare il layout di conseguenza, puoi utilizzare un BoxWithConstraints. I vincoli di misurazione si trovano nell'ambito della funzione lambda dei contenuti. Puoi utilizzare questi vincoli di misurazione per comporre layout diversi per configurazioni dello schermo diverse:

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Layout basati su slot

Compose fornisce un'ampia gamma di composabili basati su Material Design con la dipendenza androidx.compose.material:material (inclusa durante la creazione di un progetto Compose in Android Studio) per semplificare la creazione della UI. Sono forniti elementi come Drawer, FloatingActionButton e TopAppBar.

I componenti di Material Design fanno un uso intensivo delle API slot, un pattern introdotto da Compose per aggiungere un livello di personalizzazione sopra i componibili. Questo approccio rende i componenti più flessibili, in quanto accettano un elemento secondario che può configurarsi autonomamente, anziché dover esporre ogni parametro di configurazione dell'elemento secondario. Gli slot lasciano uno spazio vuoto nella UI che lo sviluppatore può riempire come preferisce. Ad esempio, questi sono gli slot che puoi personalizzare in un TopAppBar:

Un diagramma che mostra gli slot disponibili in una barra dell'app Material Components

I componibili di solito accettano una lambda componibile content ( content: @Composable () -> Unit). Le API slot espongono più parametri content per usi specifici. Ad esempio, TopAppBar ti consente di fornire i contenuti per title, navigationIcon e actions.

Ad esempio, Scaffold ti consente di implementare un'interfaccia utente con la struttura di layout di base di Material Design. Scaffoldfornisce slot per i componenti Material di primo livello più comuni, come TopAppBar, BottomAppBar, FloatingActionButton e Drawer. Utilizzando Scaffold, è facile assicurarsi che questi componenti siano posizionati correttamente e funzionino insieme nel modo giusto.

L'app di esempio JetNews, che utilizza Scaffold per posizionare più elementi

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}