Modificatori di scrittura

I modificatori ti consentono di decorare o arricchire un componibile. I modificatori ti permettono di:

  • Modificare le dimensioni, il layout, il comportamento e l'aspetto del componibile
  • Aggiungere informazioni, ad esempio le etichette di accessibilità
  • Elabora input utente
  • Aggiungi interazioni di alto livello, come rendere un elemento cliccabile, scorribile, trascinabile o zoomabile.

I modificatori sono oggetti Kotlin standard. Crea un modificatore chiamando una delle funzioni di classe Modifier:

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Due righe di testo su uno sfondo colorato, con una spaziatura interna attorno al testo.

Puoi concatenare queste funzioni per comporle:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Lo sfondo colorato dietro il testo si estende ora per tutta la larghezza del dispositivo.

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

  • padding inserisce uno spazio intorno a un elemento.
  • fillMaxWidth consente al componibile di riempire la larghezza massima assegnata dal relativo elemento principale.

Come best practice, tutti gli elementi componibili accettano un parametro modifier e lo passi al primo elemento figlio che emette UI. In questo modo il tuo codice è più riutilizzabile e il suo comportamento è più prevedibile e intuitivo. Per ulteriori informazioni, consulta le linee guida dell'API Compose, Gli elementi accettano e rispettano un parametro di modifica.

L'ordine dei modificatori è importante

L'ordine delle funzioni di modifica è significativo. Poiché ogni funzione apporta modifiche al Modifierrestituito dalla funzione precedente, la sequenza influisce sul risultato finale. Vediamo un esempio:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

L'intera area, inclusa la spaziatura interna attorno ai bordi, risponde ai clic

Nel codice sopra l'intera area è cliccabile, inclusa la spaziatura interna, perché il modificatore padding è stato applicato dopo il modificatore clickable. Se l'ordine dei modificatori viene invertito, lo spazio aggiunto da padding non reagisce all'input dell'utente:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

La spaziatura interna attorno al bordo del layout non risponde più ai clic

Modificatori integrati

Jetpack Compose fornisce un elenco di modificatori integrati per aiutarti a decorare o potenziare un componibile. Ecco alcuni comuni modificatori che utilizzerai per regolare i tuoi layout.

padding e size

Per impostazione predefinita, i layout forniti in Compose aggregano i dati secondari. Tuttavia, puoi impostare una dimensione utilizzando il modificatore size:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

Tieni presente che le dimensioni specificate potrebbero non essere rispettate se non soddisfano i vincoli derivanti dall'elemento principale del layout. Se richiedi di fissare la dimensione componibile indipendentemente dai vincoli in entrata, utilizza il modificatore requiredSize:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

L'immagine secondaria è più grande dei vincoli derivanti dall'immagine principale

In questo esempio, anche con l'elemento height principale impostato su 100.dp, l'altezza di Image sarà 150.dp, poiché il modificatore requiredSize ha la precedenza.

Se vuoi che un layout secondario riempia tutta l'altezza disponibile consentita dall'elemento principale, aggiungi il modificatore fillMaxHeight (Scrittura fornisce anche fillMaxSize e fillMaxWidth):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

L'altezza dell'immagine corrisponde a quella dell'immagine principale

Per aggiungere una spaziatura interna attorno a un elemento, imposta un modificatore padding.

Se vuoi aggiungere una spaziatura interna sopra una base di riferimento del testo in modo da raggiungere una distanza specifica dalla parte superiore del layout alla base, utilizza il modificatore paddingFromBaseline:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

Testo con spaziatura interna sopra

Offset

Per posizionare un layout rispetto alla sua posizione originale, aggiungi il modificatore offset e imposta l'offset sugli assi x e y. Gli offset possono essere sia positivi che non positivi. La differenza tra padding e offset è che l'aggiunta di un offset a un componibile non ne modifica le misure:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

Testo spostato a destra del contenitore principale

Il modificatore offset viene applicato orizzontalmente in base alla direzione del layout. In un contesto da sinistra a destra, un valore offset positivo sposta l'elemento a destra, mentre in un contesto da destra a sinistra sposta l'elemento a sinistra. Se devi impostare un offset senza considerare la direzione del layout, vedi il modificatore di absoluteOffset, in cui un valore di offset positivo sposta sempre l'elemento a destra.

Il modificatore offset fornisce due sovraccarichi: offset che prende gli offset come parametri e offset che accetta una lambda. Per informazioni più dettagliate su quando utilizzarli e su come ottimizzare le prestazioni, leggi la sezione Prestazioni di Compose - Rimanda letture il più a lungo possibile.

Sicurezza dell'ambito in Compose

In Compose esistono modificatori che possono essere utilizzati solo se applicati a elementi secondari di determinati componibili. Compose applica questa impostazione mediante ambiti personalizzati.

Ad esempio, se vuoi impostare un asset secondario delle dimensioni dell'elemento principale Box senza influire sulla dimensione Box, utilizza il modificatore matchParentSize. matchParentSize è disponibile solo in BoxScope. Di conseguenza, può essere utilizzata solo su un figlio all'interno di un elemento padre Box.

La sicurezza dell'ambito ti impedisce di aggiungere modificatori che non funzionerebbero in altri componibili e ambiti e ti fa risparmiare tempo per prove ed errori.

I modificatori con ambito informano l'elemento padre di alcune informazioni che quest'ultimo deve conoscere sull'elemento figlio. Questi sono anche comunemente indicati come modificatori dei dati principali. I loro componenti interni sono diversi dai modificatori per uso generico, ma dal punto di vista dell'utilizzo, queste differenze non sono importanti.

matchParentSize in Box

Come accennato sopra, se vuoi che un layout secondario abbia le stesse dimensioni di un layout principale Box senza influire sulle dimensioni Box, utilizza il modificatore matchParentSize.

Tieni presente che matchParentSize è disponibile solo in un ambito Box, il che significa che si applica solo agli elementi secondari diretti di elementi componibili Box.

Nell'esempio riportato di seguito, l'elemento secondario Spacer prende le dimensioni dal relativo elemento secondario Box, che a sua volta prende le dimensioni dall'elemento secondario più grande, ArtistCard in questo caso.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

Sfondo grigio che riempie il contenitore

Se venisse utilizzato fillMaxSize al posto di matchParentSize, Spacer occuperebbe tutto lo spazio disponibile consentito per l'elemento principale, causando a sua volta l'espansione e l'esaurimento dello spazio disponibile.

Sfondo grigio che riempie lo schermo

weight a Row e Column

Come hai visto nella sezione precedente su Spaziatura interna e dimensioni, per impostazione predefinita una dimensione componibile viene definita dai contenuti a cui fa riferimento. Puoi impostare una dimensione componibile in modo che sia flessibile all'interno dell'elemento padre utilizzando il modificatore weight, disponibile solo in RowScope e ColumnScope.

Prendiamo un elemento Row che contiene due elementi componibili Box. La prima casella riceve il doppio del weight del secondo, quindi viene doppiata della larghezza. Poiché Row ha una larghezza di 210.dp, la prima Box è larga 140.dp e la seconda 70.dp:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

La larghezza dell'immagine corrisponde al doppio della larghezza del testo

Estrazione e riutilizzo dei modificatori

Più modificatori possono essere concatenati per arredare o arricchire un componibile. Questa catena viene creata tramite l'interfaccia Modifier, che rappresenta un elenco ordinato e immutabile di un singolo elemento Modifier.Elements.

Ogni Modifier.Element rappresenta un comportamento individuale, ad esempio i comportamenti relativi a layout, disegno e grafica, tutti i comportamenti relativi a gesti, elementi di interesse e semantica, nonché eventi di input del dispositivo. L'ordinamento è importante: gli elementi modificatori aggiunti per primi saranno applicati per primi.

A volte può essere utile riutilizzare le stesse istanze della catena di modificatori in più elementi componibili, estraendole in variabili e spostandole in ambiti più elevati. Può migliorare la leggibilità del codice o contribuire a migliorare le prestazioni della tua app per diversi motivi:

  • La riallocazione dei modificatori non verrà ripetuta in caso di ricomposizione per gli elementi componibili che li utilizzano
  • Le catene di modificatori potrebbero essere molto lunghe e complesse, quindi il riutilizzo della stessa istanza di una catena può ridurre il carico di lavoro richiesto dal runtime di Compose per il confronto
  • Questa estrazione favorisce la pulizia, la coerenza e la manutebilità del codice in tutto il codebase

Best practice per il riutilizzo dei modificatori

Crea le tue catene Modifier ed estraile per riutilizzarle su più componenti componibili. Non è assolutamente consentito salvare un modificatore, dato che si tratta di oggetti simili ai dati:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

Estrazione e riutilizzo dei modificatori in caso di cambiamenti frequenti dello stato

Quando osservi il cambiamento frequente degli stati all'interno di elementi componibili, come gli stati di un'animazione o scrollState, è possibile che vengano eseguite una quantità significativa di ricomposizioni. In questo caso, i modificatori verranno assegnati a ogni ricomposizione e potenzialmente per ogni frame:

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

Puoi invece creare, estrarre e riutilizzare la stessa istanza del modificatore e passarla al componibile in questo modo:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

Estrazione e riutilizzo dei modificatori senza ambito

I modificatori possono limitare l'ambito o limitare l'ambito a un componibile specifico. Nel caso dei modificatori senza ambito, puoi estrarli facilmente al di fuori di qualsiasi componibile come semplice variabili:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

Ciò può essere particolarmente utile se combinato con i layout lenti. Nella maggior parte dei casi, tutti gli elementi, potenzialmente significativi, avranno gli stessi modificatori:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

Estrazione e riutilizzo dei modificatori basati sull'ambito

Quando hai a che fare con modificatori che hanno come ambito determinati componibili, puoi estrarli al livello più alto possibile e riutilizzarli dove appropriato:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

Devi passare solo i modificatori con ambito estratti agli elementi secondari diretti con lo stesso ambito. Per ulteriori riferimenti sul motivo per cui è importante, consulta la sezione Sicurezza dell'ambito in Compose:

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

Ulteriore concatenazione dei modificatori estratti

Puoi concatenare o aggiungere ulteriormente le catene di modificatori estratte chiamando la funzione .then():

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

Tieni solo presente che l'ordine dei modificatori è importante.

Scopri di più

Forniamo un elenco completo dei modificatori, con i relativi parametri e ambiti.

Per ulteriore pratica sull'utilizzo dei modificatori, puoi anche consultare la pagina Layout di base nel codelab in Compose o consultare la pagina Repository Ora in Android.

Per saperne di più sui modificatori personalizzati e su come crearli, consulta la documentazione su Layout personalizzati - Utilizzo del modificatore di layout.