Nozioni di base sul layout di Scrivi

Con Jetpack Compose, è molto più semplice progettare e creare l'UI della tua app. Compose trasforma lo stato in elementi UI tramite:

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

Componi lo stato trasformandolo in UI tramite composizione, layout, disegno

Questo documento è incentrato sul layout degli elementi e spiega alcuni dei componenti di base forniti da Compose per aiutarti a disporre gli elementi dell'interfaccia utente.

Obiettivi dei layout in Compose

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

Nozioni di base sulle funzioni componibili

Le funzioni componibili sono l'elemento di base di base di Compose. Una funzione componibile è una funzione che emette Unit che descrive parte della UI. La funzione riceve un input e genera ciò che viene mostrato sullo schermo. Per ulteriori informazioni sugli elementi componibili, consulta la documentazione di Compose mental model.

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

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

Senza indicazioni su come vuoi che vengano disposti, Compose impila gli elementi di testo uno sopra l'altro, rendendoli illeggibili:

Due elementi di testo disegnati uno sopra l'altro, per rendere il testo illeggibile.

Compose fornisce una raccolta di layout pronti all'uso per aiutarti a organizzare gli elementi dell'interfaccia utente e a definire layout più specifici.

Componenti del layout standard

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

Utilizza Column per posizionare gli elementi in verticale 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.

Analogamente, 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 grafica accanto a una colonna di elementi di testo

Utilizza Box per posizionare gli elementi sopra un altro. Box supporta anche la configurazione dell'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 l'uno sull'altro

Spesso questi componenti di base sono sufficienti. Puoi scrivere la tua funzione componibile per combinare questi layout in un layout più elaborato adatto alla tua app.

Confronta tre elementi componibili semplici di layout: colonna, riga e casella

Per impostare la posizione dei bambini all'interno di un elemento Row, imposta gli argomenti horizontalArrangement e verticalAlignment. Per 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, la struttura ad albero dell'interfaccia utente è disposti in un'unica passaggio. A ogni nodo viene prima chiesto di misurare se stesso, quindi misurare eventuali figli in modo ricorsivo, passando i vincoli di dimensione dell'albero agli elementi secondari. Quindi, vengono ridimensionati e posizionati i nodi foglia, con le dimensioni risolte e le istruzioni di posizionamento ritrasmessi nell'albero.

In breve, i genitori misurano la misurazione prima dei figli, ma la loro taglia e la loro posizione dopo i loro figli.

Considera la seguente funzione SearchResult.

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

Questa funzione restituisce il seguente albero dell'interfaccia utente.

SearchResult
  Row
    Image
    Column
      Text
      Text

Nell'esempio SearchResult, il layout ad albero dell'interfaccia utente segue questo ordine:

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

Ordine di misurazione, dimensionamento e posizionamento nella struttura dell'interfaccia utente dei risultati di ricerca

Esibizione

Compose raggiunge prestazioni elevate misurando i bambini una sola volta. La misurazione a passaggio singolo è un'ottima soluzione per le prestazioni, consentendo a Compose di gestire in modo efficiente alberi UI profonde. Se un elemento misurasse l'elemento figlio due volte e quest'ultimo ne misurasse ciascuno due volte e così via, un singolo tentativo di strutturare un'intera UI avrebbe richiesto molto lavoro, rendendo difficile mantenere le prestazioni dell'app.

Se per qualche motivo il layout richiede più misurazioni, Compose offre un sistema speciale: le misure intrinseche. Per saperne di più su questa funzionalità, consulta Misurazioni intrinseche nei layout di Scrivi.

Poiché la misurazione e il posizionamento sono sottofasi distinte del passaggio del layout, qualsiasi modifica che interessa solo il posizionamento degli elementi, non la misurazione, può essere eseguita separatamente.

Utilizzare i modificatori nei layout

Come spiegato in Modificatori di Scrivi, puoi utilizzarli per decorare o arricchire i tuoi componibili. 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, con l'uso di modificatori per cambiare la disposizione dei grafici e le aree che rispondono all'input dell'utente

Nel codice riportato sopra, puoi notare che le diverse funzioni di modifica sono utilizzate insieme.

  • clickable genera una reazione componibile all'input dell'utente e mostra un'onda.
  • padding inserisce uno spazio intorno a un elemento.
  • fillMaxWidth consente al componibile di riempire la larghezza massima assegnata dal relativo elemento principale.
  • size() specifica la larghezza e l'altezza preferite per un elemento.

Layout scorrevoli

Scopri di più sui layout scorrevoli nella documentazione relativa ai gesti di Scrivi.

Per gli elenchi e gli elenchi lenti, consulta la documentazione relativa agli elenchi di composizione.

Layout adattabili

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

Vincoli

Per conoscere i vincoli derivanti dall'elemento padre 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 offre un'ampia gamma di elementi componibili basati su Material Design con la dipendenza androidx.compose.material:material (inclusa quando si crea un progetto Scrivi in Android Studio) per semplificare la creazione di UI. Elementi come Drawer, FloatingActionButton e TopAppBar sono tutti forniti.

I componenti del materiale fanno un uso intensivo delle API slot, un pattern introdotto da Compose per aggiungere un livello di personalizzazione ai componenti componibili. Questo approccio rende i componenti più flessibili, poiché accettano un elemento secondario che può configurare da sé, invece di dover esporre ogni parametro di configurazione dell'elemento figlio. Le aree lasciano uno spazio vuoto nell'interfaccia utente che lo sviluppatore potrà riempire come desidera. Ad esempio, di seguito sono riportate le aree che puoi personalizzare in una TopAppBar:

Diagramma che mostra gli slot disponibili in una barra delle app di Material Componenti

I componibili di solito utilizzano una funzione 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 di title, navigationIcon e actions.

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

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

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