Modificatori di scrittura

I modificatori ti consentono di decorare o aumentare un composable. I modificatori ti consentono di fare questo tipo di cose:

  • Modificare le dimensioni, il layout, il comportamento e l'aspetto del composable
  • Aggiungere informazioni, ad esempio le etichette di accessibilità
  • Elabora l'input dell'utente
  • Aggiungi interazioni di alto livello, ad esempio rendendo un elemento selezionabile, scorrevole, trascinabile o con zoom

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 spaziatura 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 l'intera larghezza del dispositivo.

Nel codice riportato sopra, noterai diverse funzioni di modificatore utilizzate insieme.

  • padding inserisce uno spazio intorno a un elemento.
  • fillMaxWidth fa in modo che il composable raggiunga la larghezza massima assegnata dall'elemento principale.

È buona prassi che tutti i composabili accettino un parametro modifier e trasmettano questo modificatore al primo elemento secondario che emette l'interfaccia utente. In questo modo, il codice sarà più riutilizzabile e il suo comportamento sarà più prevedibile e intuitivo. Per maggiori 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 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 la spaziatura intorno ai bordi, risponde ai clic

Nel codice riportato sopra, l'intera area è selezionabile, incluso il padding circostante, perché il modificatore padding è stato applicato dopo il modificatore clickable. Se l'ordine degli modificatori è 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
    }
}

I margini intorno al bordo del layout non rispondono più ai clic

Modificatori integrati

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

padding e size

Per impostazione predefinita, i layout forniti in Compose inseriscono un a capo nei 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 del composable 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'elemento 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 layout principale, 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 è uguale a quella dell'elemento principale

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

Se vuoi aggiungere un'area di a capo 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 spaziatura sopra

Offset

Per posizionare un layout rispetto alla sua posizione originale, aggiungi il modificatore offset e imposta l'offset sull'asse x e y. Gli offset possono essere positivi o non positivi. La differenza tra padding e offset è che l'aggiunta di un offset a un composable non ne cambia 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 offset positivo sposta l'elemento verso destra, mentre in un contesto da destra a sinistra, lo sposta verso 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 verso la sinistra.

Il modificatore offset fornisce due sovraccarichi: offset che prende gli offset come parametri e offset che prende una lambda. Per informazioni più dettagliate su quando utilizzare ciascuna di queste opzioni e su come ottimizzare il rendimento, consulta la sezione Rendimento di Compose: rimanda le letture il più a lungo possibile.

Sicurezza dell'ambito in Compose

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

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

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

I modificatori basati sugli ambiti informano il genitore su alcune informazioni che deve conoscere sul figlio. Sono noti anche come modificatori dei dati principali. I relativi componenti interni sono diversi da quelli dei modificatori di uso generale, 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 principaleBox senza influire sulle dimensioni di Box, utilizza il modificatore matchParentSize.

Tieni presente che matchParentSize è disponibile solo nell'ambito di un ambito Box, il che significa che si applica solo agli elementi secondari diretti dei composabili Box.

Nell'esempio seguente, l'elemento secondario Spacer eredita le dimensioni dall'elemento principale Box, che a sua volta le eredita dall'elemento secondario più grande, in questo caso ArtistCard.

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

Sfondo grigio che riempie il contenitore

Se al posto di matchParentSize fosse stato utilizzato fillMaxSize, Spacer occuperebbe tutto lo spazio disponibile consentito al contenitore, che a sua volta si espanderebbe e riempirebbe tutto lo spazio disponibile.

Sfondo grigio che riempie lo schermo

weight in Row e Column

Come hai visto nella sezione precedente relativa a Spaziatura interna e dimensione, per impostazione predefinita, le dimensioni di un componente componibile sono definite dai contenuti che vengono a capo. Puoi impostare le dimensioni di un componibile in modo che siano flessibili all'interno del suo elemento primario utilizzando il modificatore weight, disponibile solo in RowScope e ColumnScope.

Prendiamo un Row contenente due composabili Box. La prima casella ha un weight doppio rispetto alla seconda, quindi ha anche 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 migliorare un composable. Questa catena viene creata tramite l'interfaccia Modifier, che rappresenta un elenco ordinato e immutabile di singoli Modifier.Elements.

Ogni Modifier.Element rappresenta un singolo comportamento, come i comportamenti relativi a layout, disegno e grafica, tutti i comportamenti relativi a gesti, messa a fuoco e semantica, nonché gli 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 di catena di modificatori in più composabili estraendoli in variabili e aggiungendoli a ambiti superiori. 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 quando si verifica la ricompozione per i composabili che li utilizzano
  • Le catene di modificatori potrebbero essere potenzialmente molto lunghe e complesse, quindi il riutilizzo della stessa istanza di una catena può alleggerire il carico di lavoro necessario per il runtime di Compose quando le confronta.
  • Questa estrazione favorisce la pulizia, la coerenza e la manutenibilità del codice in tutta la base di codice

Best practice per il riutilizzo dei modificatori

Crea le tue catene Modifier ed estraile per riutilizzarle su più componenti composibili. È perfettamente possibile salvare solo un modificatore, poiché si tratta di oggetti simili ai dati:

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

Estrazione e riutilizzo dei modificatori quando si osserva uno stato in continua evoluzione

Quando si osservano stati che cambiano di frequente all'interno dei composabili, come gli stati di animazione o scrollState, può essere eseguita una quantità significativa di ricostruzioni. In questo caso, i modificatori verranno allocati a ogni ricomposizio e potenzialmente per ogni fotogramma:

@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 composable come segue:

// 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 di modificatori senza ambito

I modificatori possono essere senza ambito o avere un ambito specifico per un composable. Nel caso dei modificatori senza ambito, puoi estrarli facilmente al di fuori di qualsiasi composable come semplici variabili:

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

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

Questa funzionalità può essere particolarmente utile se combinata con i layout lazy. Nella maggior parte dei casi, è consigliabile che tutti gli elementi, potenzialmente significativi, abbiano 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 sugli ambiti

Quando hai a che fare con modificatori che hanno come ambito determinati composabili, puoi estrarli al livello più alto possibile e riutilizzarli ove 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 passare i modificatori con ambito estratto solo ai figli diretti con lo stesso ambito. Per ulteriori informazioni sul motivo per cui questo è 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
            // ...
        )
    }
}

Catena aggiuntiva di modificatori estratti

Puoi concatenare ulteriormente o aggiungere le catene di modificatori estratti 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 sull'utilizzo dei modificatori, puoi anche consultare il codelab sui layout di base in Compose o fare riferimento al repository Ora in Android.

Per ulteriori informazioni sui modificatori personalizzati e su come crearli, consulta la documentazione su Layout personalizzati - Utilizzo del modificatore del layout.