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 degli elementi
  2. Layout degli elementi
  3. Disegno degli elementi

Comporre lo stato di trasformazione nell'UI tramite composizione, layout e disegno

Questo documento si concentra sul layout degli elementi, spiegando alcuni dei blocchi di costruzione forniti da Compose per aiutarti a disporre gli elementi dell'interfaccia utente.

Obiettivi dei layout in Scrivi

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 emette Unit che descrive una parte della tua UI. La funzione accetta alcuni input e genera ciò che viene mostrato sullo schermo. Per saperne di più sui composable, consulta la documentazione sul modello mentale di Compose.

Una funzione componibile può emettere diversi elementi dell'interfaccia utente. 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, Crea impila gli elementi di testo uno sopra l'altro, rendendoli illeggibili:

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

Compose offre una raccolta di layout pronti all'uso per aiutarti a disporre gli elementi dell'interfaccia utente e semplifica la definizione di layout più specializzati.

Componenti del 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 colonna, in modo che il testo sia leggibile

Allo stesso modo, utilizza Row per posizionare gli elementi orizzontalmente 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 una piccola immagine accanto a una colonna di elementi di testo

Utilizza Box per sovrapporre gli elementi. 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 sopra l'altro

Spesso questi elementi 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 semplici composable di layout: colonna, riga e casella

Per impostare la posizione dei bambini 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 prima chiesto di misurarsi, poi di misurare ricorsivamente gli elementi secondari, passando i vincoli di dimensione verso il basso dell'albero agli elementi secondari. Quindi, le dimensioni e il posizionamento dei nodi foglia vengono risolti e le istruzioni relative a dimensioni e posizionamento vengono trasmesse di nuovo all'albero.

In breve, i genitori vengono misurati prima dei figli, ma vengono posizionati dopo i figli.

Considera la seguente funzione SearchResult.

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

Questa funzione genera il seguente albero dell'interfaccia utente.

SearchResult
  Row
    Image
    Column
      Text
      Text

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

  1. Al nodo principale Row viene chiesto di eseguire la misurazione.
  2. Il nodo radice Row chiede al suo primo elemento secondario, Image, di misurare.
  3. Image è un nodo foglia (ovvero non ha elementi secondari), quindi segnala una dimensione e restituisce le istruzioni di posizionamento.
  4. Il nodo radice Row chiede al suo secondo figlio, Column, di misurare.
  5. Il nodo Column chiede al suo primo elemento secondario Text di eseguire la misurazione.
  6. Il primo nodo Text è un nodo foglia, quindi segnala una dimensione e restituisce le istruzioni di posizionamento.
  7. Il nodo Column chiede al secondo nodo secondario Text di eseguire la misurazione.
  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 figli, può determinare le proprie dimensioni e il proprio posizionamento.
  10. Ora che il nodo radice Row ha misurato, dimensionato e posizionato i suoi figli, può determinare le proprie dimensioni e il proprio posizionamento.

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

Prestazioni

Compose raggiunge prestazioni elevate misurando i figli una sola volta. La misurazione a singola passata è utile per le prestazioni, in quanto consente a Compose di gestire in modo efficiente alberi dell'interfaccia utente complessi. Se un elemento ha misurato il suo elemento secondario due volte e quest'ultimo ha misurato ciascuno dei suoi elementi secondari due volte e così via, un singolo tentativo di disporre un'intera UI dovrebbe richiedere molto lavoro, rendendo difficile mantenere le prestazioni dell'app.

Se il layout richiede più misurazioni per qualche motivo, 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 del passaggio del layout, qualsiasi modifica che influisce solo sul posizionamento degli elementi, non sulla misurazione, può essere eseguita separatamente.

Utilizzare i modificatori nei layout

Come descritto in Modificatori di composizione, puoi utilizzare i modificatori per decorare o aumentare i tuoi composable. I modificatori sono essenziali per personalizzare il layout. Ad esempio, qui concateniamo diversi modificatori per personalizzare 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 modificatori per cambiare la disposizione della grafica e le aree che rispondono all'input dell'utente

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

  • clickable rende un elemento componibile reattivo all'input dell'utente e mostra un'increspatura.
  • padding aggiunge spazio intorno a un elemento.
  • fillMaxWidth fa in modo che il componibile riempia la larghezza massima che gli viene assegnata dal relativo elemento padre.
  • size() specifica la larghezza e l'altezza preferite di un elemento.

Layout scorrevoli

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

Per elenchi e elenchi pigri, consulta la documentazione su come comporre elenchi.

Layout adattabili

Un layout deve essere progettato tenendo conto dei diversi orientamenti dello schermo e delle dimensioni del fattore di forma. Compose offre alcuni meccanismi predefiniti per facilitare l'adattamento dei layout componibili a varie configurazioni dello schermo.

Vincoli

Per conoscere i vincoli del contenitore principale e progettare il layout di conseguenza, puoi utilizzare un BoxWithConstraints. I vincoli di misurazione sono disponibili nell'ambito della 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 sugli slot

Compose offre un'ampia gamma di componenti componibili basati su Material Design con la dipendenza androidx.compose.material:material (inclusa quando crei un progetto Compose in Android Studio) per semplificare la creazione dell'interfaccia utente. Sono forniti tutti gli elementi come Drawer, FloatingActionButton, e TopAppBar.

I componenti Material utilizzano molto le API slot, un pattern introdotto da Compose per aggiungere un livello di personalizzazione sopra i composable. Questo approccio rende i componenti più flessibili, in quanto accettano un elemento secondario che può configurarsi autonomamente anziché dover esporre ogni parametro di configurazione del componente secondario. Gli slot lasciano uno spazio vuoto nell'interfaccia utente che lo sviluppatore può riempire a suo piacimento. 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 composable in genere accettano una lambda composable ( content: @Composable () -> Unit). Le API slot espongono più parametri content per usi specifici.content Ad esempio, TopAppBar ti consente di fornire i contenuti per title, navigationIcon e actions.

Ad esempio, Scaffold ti consente di implementare una UI 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 in modo corretto.

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

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