Layout personalizzati

In Compose, gli elementi dell'UI sono rappresentati dalle funzioni componibili che emettono una parte dell'UI quando vengono richiamate, che viene poi aggiunta a un albero dell'UI sottoposto a rendering sullo schermo. Ogni elemento dell'UI ha un elemento principale e potenzialmente molti elementi secondari. Ogni elemento si trova anche all'interno del suo elemento principale, specificato come posizione (x, y) e dimensione, specificata come width e height.

Gli elementi principali definiscono i vincoli per gli elementi secondari. A un elemento viene chiesto di definire le proprie dimensioni all'interno di questi vincoli. I vincoli limitano la width e la height minima e massima di un elemento. Se un elemento ha elementi secondari, può misurare ogni elemento secondario per determinare le proprie dimensioni. Una volta che un elemento determina e segnala le proprie dimensioni, ha la possibilità di definire come posizionare i suoi elementi secondari rispetto a se stesso, come descritto in dettaglio in Creazione di layout personalizzati.

La disposizione di ogni nodo nell'albero dell'UI è un processo in tre passaggi. Ogni nodo deve:

  1. Misurare gli elementi secondari
  2. Decidere le proprie dimensioni
  3. Posizionare gli elementi secondari
Tre passaggi del layout dei nodi: misura i figli, decidi le dimensioni, posiziona i figli
Figura 1. I tre passaggi del layout dei nodi sono misurare gli elementi secondari, decidere le dimensioni, e posizionare gli elementi secondari.

L'utilizzo degli ambiti definisce quando puoi misurare e posizionare gli elementi secondari. La misurazione di un layout può essere eseguita solo durante i passaggi di misurazione e layout e un elemento secondario può essere posizionato solo durante i passaggi di layout (e solo dopo essere stato misurato). Grazie agli ambiti di Compose come MeasureScope, e PlacementScope, questa operazione viene applicata in tempo di compilazione.

Utilizzare il modificatore di layout

Puoi utilizzare il modificatore layout per modificare la modalità di misurazione e disposizione di un elemento. Layout è un'espressione lambda; i suoi parametri includono l'elemento che puoi misurare, passato come measurable, e i vincoli in entrata di questo elemento componibile, passati come constraints. Un modificatore di layout personalizzato può avere il seguente aspetto:

fun Modifier.customLayoutModifier() =
    layout { measurable, constraints ->
        // ...
    }

Visualizza un Text sullo schermo e controlla la distanza dalla parte superiore alla linea di base della prima riga di testo. Questo è esattamente ciò che fa il modificatore paddingFromBaseline; qui lo implementi come esempio. Per farlo, utilizza il modificatore layout per posizionare manualmente l'elemento componibile sullo schermo. Ecco il comportamento risultante in cui il padding superiore di Text è impostato su 24.dp:

Mostra la differenza tra il padding normale dell'interfaccia utente, che imposta lo spazio tra gli elementi, e il padding del testo, che imposta lo spazio da una linea di base all'altra
Figura 2. Testo con paddingFromBaseline applicato.

Ecco il codice per produrre questa spaziatura:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }
}

Ecco cosa succede in questo codice:

  1. Nel parametro lambda measurable, misuri il Text rappresentato da l parametro misurabile chiamando measurable.measure(constraints).
  2. Specifichi le dimensioni dell'elemento componibile chiamando il metodo layout(width, height), che fornisce anche un'espressione lambda utilizzata per posizionare gli elementi inclusi. In questo caso, è l'altezza tra l'ultima linea di base e il padding superiore aggiunto.
  3. Posizioni gli elementi inclusi sullo schermo chiamando placeable.place(x, y). Se gli elementi inclusi non vengono posizionati, non saranno visibili. La posizione y corrisponde al padding superiore: la posizione della prima linea di base del testo.

Per verificare che funzioni come previsto, utilizza questo modificatore su un Text:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

Diverse anteprime di elementi di testo: una mostra la spaziatura interna ordinaria tra gli elementi, l'altra mostra la spaziatura interna da una linea di base all'altra.
Figura 3. Modificatore applicato a un elemento componibile Text e visualizzato in anteprima.

Creare layout personalizzati

Il modificatore layout modifica solo l'elemento componibile chiamante. Per misurare e disporre più elementi componibili, utilizza invece l'elemento componibile Layout. Questo elemento componibile ti consente di misurare e disporre manualmente gli elementi secondari. Tutti i layout di livello superiore come Column e Row sono creati con l'elemento componibile Layout.

Questo esempio crea una versione molto semplice di Column. La maggior parte dei layout personalizzati segue questo pattern:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
        // ...
    }
}

Analogamente al modificatore layout, measurables è l'elenco degli elementi secondari da misurare e constraints sono i vincoli dell'elemento principale. Seguendo la stessa logica di prima, MyBasicColumn può essere implementato come segue:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

Gli elementi componibili secondari sono vincolati dai vincoli Layout (senza i vincoli minHeight) e vengono posizionati in base a yPosition dell'elemento componibile precedente.

Ecco come verrebbe utilizzato questo elemento componibile personalizzato:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text("MyBasicColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

Diversi elementi di testo impilati uno sopra l'altro in una colonna.
Figura 4. Implementazione personalizzata di Column.

Direzione del layout

Modifica la direzione del layout di un elemento componibile modificando la LocalLayoutDirection composizione locale.

Se posizioni manualmente gli elementi componibili sullo schermo, il LayoutDirection fa parte del LayoutScope del modificatore layout o dell'elemento componibile Layout.

Quando utilizzi layoutDirection, posiziona gli elementi componibili utilizzando place. A differenza del placeRelative metodo, place non cambia in base alla direzione del layout (da sinistra a destra o da destra a sinistra).

Layout personalizzati in azione

Scopri di più sui layout e sui modificatori in Layout di base in Compose, e guarda i layout personalizzati in azione negli esempi di Compose che creano layout personalizzati.

Scopri di più

Per scoprire di più sui layout personalizzati in Compose, consulta le seguenti risorse aggiuntive.

Video