Modificatori di scrittura

I modificatori ti consentono di decorare o aumentare un elemento componibile. I modificatori ti consentono di fare cose come:

  • Modificare le dimensioni, il layout, il comportamento e l'aspetto del componente componibile
  • Aggiungere informazioni, ad esempio etichette di accessibilità
  • Elaborare l'input dell'utente
  • Aggiungi interazioni di alto livello, ad esempio rendi un elemento selezionabile, scorrevole, trascinabile o ingrandibile

I modificatori sono oggetti Kotlin standard. Crea un modificatore chiamando una delle funzioni della 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 spaziatura interna intorno 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 ora si estende per tutta la larghezza del dispositivo.

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

  • 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.

È una best practice che tutti i tuoi componenti componibili accettino un parametro modifier e passino questo modificatore al primo elemento secondario che emette l'interfaccia utente. In questo modo, il codice è più riutilizzabile e il suo comportamento è più prevedibile e intuitivo. Per maggiori informazioni, consulta le linee guida dell'API Compose, Gli elementi accettano e rispettano un parametro Modifier.

L'ordine dei modificatori è importante

L'ordine delle funzioni modificatore è importante. Poiché ogni funzione apporta modifiche al valore Modifier restituito 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 l'imbottitura intorno ai bordi, risponde ai clic

Nel codice sopra, l'intera area è selezionabile, incluso il padding circostante, 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
    }
}

Il padding intorno al bordo del layout non risponde più ai clic

Modificatori integrati

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

padding e size

Per impostazione predefinita, i layout forniti in Compose eseguono il wrapping dei relativi elementi 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 del layout principale. Se vuoi che le dimensioni componibili siano fisse 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 provenienti dall'immagine principale

In questo esempio, anche se l'elemento principale height è 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 dal genitore, aggiungi il modificatore fillMaxHeight (Compose 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 è pari a quella dell'elemento principale

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

Se vuoi aggiungere un riempimento sopra una linea di base del testo in modo da ottenere una distanza specifica dalla parte superiore del layout alla linea di 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 riempimento sopra

Offset

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

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

Testo spostato sul lato destro del contenitore principale

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

Il modificatore offset fornisce due overload: offset che accetta gli offset come parametri e offset che accetta una lambda. Per informazioni più dettagliate su quando utilizzare ciascuna di queste opzioni e su come ottimizzare il rendimento, leggi la sezione Composizione delle prestazioni: posticipa le letture il più a lungo possibile.

Sicurezza degli ambiti in Compose

In Compose, esistono modificatori che possono essere utilizzati solo se applicati ai figli di determinati composable. Compose lo applica tramite ambiti personalizzati.

Ad esempio, se vuoi rendere un elemento figlio grande quanto l'elemento padre Box senza influire sulle dimensioni di Box, utilizza il modificatore matchParentSize. matchParentSize è disponibile solo in BoxScope. Pertanto, può essere utilizzato solo su un bambino all'interno di un genitore Box.

La sicurezza dell'ambito ti impedisce di aggiungere modificatori che non funzionerebbero in altri composizioni e ambiti e ti fa risparmiare tempo evitando tentativi ed errori.

I modificatori con ambito comunicano al genitore alcune informazioni che deve sapere sul figlio. Questi vengono anche comunemente chiamati modificatori dei dati principali. Il loro funzionamento interno è diverso dai modificatori generici, ma dal punto di vista dell'utilizzo, queste differenze non contano.

matchParentSize in Box

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

Tieni presente che matchParentSize è disponibile solo all'interno di un ambito Box, il che significa che si applica solo agli elementi secondari diretti dei componenti componibili Box.

Nell'esempio seguente, l'elemento secondario Spacer prende le dimensioni dall'elemento principale Box, che a sua volta prende le dimensioni dagli elementi secondari più grandi, ArtistCard in questo caso.

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

Sfondo grigio che riempie il contenitore

Se fosse stato utilizzato fillMaxSize anziché matchParentSize, Spacer avrebbe occupato tutto lo spazio disponibile consentito al genitore, il quale si sarebbe espanso e avrebbe riempito tutto lo spazio disponibile.

Sfondo grigio che riempie lo schermo

weight in Row e Column

Come hai visto nella sezione precedente su Spaziatura interna e dimensioni, per impostazione predefinita, le dimensioni di un elemento componibile sono definite dal contenuto che racchiude. Puoi impostare le dimensioni di un componibile in modo che siano flessibili all'interno del relativo elemento padre utilizzando il modificatore weight disponibile solo in RowScope e ColumnScope.

Prendiamo un Row che contiene due composable Box. La prima casella ha una weight doppia rispetto alla seconda, quindi ha una larghezza doppia. Poiché Row è largo 210.dp, il primo Box è largo 140.dp e il secondo è 70.dp:

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

La larghezza dell'immagine è il doppio della larghezza del testo

Estrazione e riutilizzo dei modificatori

È possibile concatenare più modificatori per decorare o aumentare un elemento componibile. Questa catena viene creata tramite l'interfaccia Modifier, che rappresenta un elenco ordinato e immutabile di singoli Modifier.Elements.

Ogni Modifier.Element rappresenta un comportamento individuale, come layout, disegno e grafica, tutti relativi ai gesti, ai comportamenti di messa a fuoco e semantici, nonché agli eventi di input del dispositivo. L'ordine è importante: gli elementi modificatori aggiunti per primi verranno applicati per primi.

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

  • La riassegnazione dei modificatori non verrà ripetuta quando si verifica la ricomposizione per i composable che li utilizzano
  • Le catene di modificatori potrebbero essere molto lunghe e complesse, quindi il riutilizzo della stessa istanza di una catena può alleviare il carico di lavoro che deve svolgere il runtime di Compose quando le confronta.
  • Questa estrazione promuove la pulizia, la coerenza e la manutenibilità del codice in tutto il codebase

Best practice per il riutilizzo dei modificatori

Crea le tue catene Modifier ed estraile per riutilizzarle in più componenti componibili. È perfettamente accettabile salvare solo un modificatore, in quanto si tratta di oggetti simili a dati:

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

Estrazione e riutilizzo dei modificatori quando si osserva uno stato che cambia frequentemente

Quando si osservano stati che cambiano frequentemente all'interno dei composable, come gli stati di animazione o scrollState, possono essere eseguite un numero significativo di ricomposizioni. In questo caso, i modificatori verranno allocati 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
    )
}

In alternativa, puoi creare, estrarre e riutilizzare la stessa istanza del modificatore e passarla al componibile nel seguente 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 essere senza ambito o con ambito per un componente componibile specifico. Nel caso di modificatori senza ambito, puoi estrarli facilmente al di fuori di qualsiasi elemento componibile come semplici 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 pigri. Nella maggior parte dei casi, vorrai che tutti i tuoi articoli, potenzialmente in quantità significativa, abbiano esattamente 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 con ambito

Quando gestisci modificatori con ambito limitato a determinati componenti combinabili, puoi estrarli al livello più alto possibile e riutilizzarli dove opportuno:

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 trasmettere i modificatori estratti e con ambito solo ai figli diretti con lo stesso ambito. Per ulteriori informazioni sull'importanza di questo aspetto, consulta la sezione Sicurezza dell'ambito in Componi:

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 presente che l'ordine dei modificatori è importante.

Scopri di più

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

Per esercitarti ulteriormente su come utilizzare i modificatori, puoi anche consultare il codelab sui layout di base in Compose o fare riferimento al repository Now in Android.

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