Tutorial

Tutorial su Jetpack Compose

Jetpack Compose è un toolkit moderno per la creazione di una UI nativa di Android. Jetpack Compose semplifica e accelera lo sviluppo di UI su Android con meno codice, strumenti potenti e API Kotlin intuitive.

In questo tutorial creerai un semplice componente dell'interfaccia utente con funzioni dichiarative. Non modificherai layout XML né utilizzerai l'Editor layout. Chiamerai le funzioni componibili per definire gli elementi che vuoi e il compilatore Compose farà il resto.

Anteprima completa
Anteprima completa

Lezione 1: Funzioni componibili

Jetpack Compose è basato su funzioni componibili. Queste funzioni ti consentono di definire la UI della tua app in modo programmatico, descrivendone l'aspetto e fornendo dipendenze dei dati, anziché concentrarti sul processo di creazione della UI (inizializzazione di un elemento, collegamento a un elemento padre e così via). Per creare una funzione componibile, è sufficiente aggiungere l'annotazione @Composable al nome della funzione.

Aggiungi un elemento di testo

Per iniziare, scarica la versione più recente di Android Studio, crea un'app selezionando Nuovo progetto e, nella categoria Telefono e tablet, seleziona Attività vuota. Assegna all'app il nome ComposeTutorial e fai clic su Fine. Il modello predefinito contiene già alcuni elementi di Compose, ma in questo tutorial imparerai a crearlo passo dopo passo.

Per prima cosa, visualizza un testo "Hello world!" aggiungendo un elemento di testo all'interno del metodo onCreate. Per farlo, definisci un blocco di contenuti e chiama la funzione componibile Text. Il blocco setContent definisce il layout dell'attività in cui vengono chiamate le funzioni componibili. Le funzioni componibili possono essere chiamate solo da altre funzioni componibili.

Jetpack Compose utilizza un plug-in di compilazione Kotlin per trasformare queste funzioni componibili negli elementi UI dell'app. Ad esempio, la funzione componibile Text definita dalla libreria dell'interfaccia utente di Compose mostra un'etichetta di testo sullo schermo.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostra anteprima
nascondi anteprima

Definire una funzione componibile

Per rendere componibile una funzione, aggiungi l'annotazione @Composable. Per provare questa funzionalità, definisci una funzione MessageCard a cui viene trasmesso un nome e la utilizza per configurare l'elemento di testo.

// ...
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

  
mostra anteprima
nascondi anteprima

Visualizzare l'anteprima della funzione in Android Studio

L'annotazione @Preview consente di visualizzare l'anteprima delle funzioni componibili in Android Studio senza dover creare e installare l'app in un emulatore o un dispositivo Android. L'annotazione deve essere utilizzata in una funzione componibile che non accetta parametri. Per questo motivo, non puoi visualizzare direttamente la funzione MessageCard. Crea invece una seconda funzione denominata PreviewMessageCard, che chiama MessageCard con un parametro appropriato. Aggiungi l'annotazione @Preview prima di @Composable.

// ...
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
mostra anteprima
nascondi anteprima

Ricrea il progetto. L'app non cambia, poiché la nuova funzione PreviewMessageCard non viene chiamata da nessuna parte, ma Android Studio aggiunge una finestra di anteprima che puoi espandere facendo clic sulla visualizzazione suddivisa (progettazione/codice). Questa finestra mostra un'anteprima degli elementi dell'interfaccia utente creati dalle funzioni componibili contrassegnati con l'annotazione @Preview. Per aggiornare le anteprime in qualsiasi momento, fai clic sul pulsante di aggiornamento nella parte superiore della finestra di anteprima.

Anteprima di una funzione componibile in Android Studio
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostra anteprima
nascondi anteprima
// ...
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

  
mostra anteprima
nascondi anteprima
// ...
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
mostra anteprima
nascondi anteprima
Anteprima di una funzione componibile in Android Studio

Lezione 2: Layout

Gli elementi dell'interfaccia utente sono gerarchici, con elementi contenuti in altri elementi. In Compose, puoi creare una gerarchia di UI richiamando funzioni componibili da altre funzioni componibili.

Aggiungere più testi

Finora hai creato la tua prima funzione componibile e la tua prima anteprima. Per scoprire altre funzionalità di Jetpack Compose, creerai una semplice schermata di messaggistica contenente un elenco di messaggi che possono essere espansi con alcune animazioni.

Per iniziare, rendi il messaggio componibile più completo mostrando il nome dell'autore e i contenuti di un messaggio. Prima di tutto devi modificare il parametro componibile in modo da accettare un oggetto Message anziché un oggetto String, quindi aggiungere un altro componibile Text all'interno dell'oggetto componibile MessageCard. Assicurati di aggiornare anche l'anteprima.

// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
mostra anteprima
nascondi anteprima

Questo codice crea due elementi di testo all'interno della visualizzazione dei contenuti. Tuttavia, poiché non hai fornito informazioni su come disporli, gli elementi di testo vengono disegnati uno sopra l'altro, rendendo il testo illeggibile.

Utilizzo di una colonna

La funzione Column consente di disporre gli elementi in verticale. Aggiungi Column alla funzione MessageCard.
Puoi utilizzare Row per disporre gli elementi orizzontalmente e Box per impilare gli elementi.

// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

mostra anteprima
nascondi anteprima

Aggiungi un elemento immagine

Arricchisci la scheda del messaggio aggiungendo un'immagine del profilo del mittente. Utilizza Resource Manager per importare un'immagine dalla tua raccolta fotografica oppure usa questa. Aggiungi un elemento componibile Row in modo che abbia un design ben strutturato e un elemento componibile Image al suo interno.

// ...
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.res.painterResource

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
mostra anteprima
nascondi anteprima

Configurare il layout

Il layout del messaggio ha la struttura giusta, ma gli elementi non sono ben distanziati e l'immagine è troppo grande. Per decorare o configurare un componibile, Compose utilizza dei modificatori. Consentono di modificare le dimensioni, il layout e l'aspetto del componibile oppure di aggiungere interazioni di alto livello, ad esempio rendendo cliccabile un elemento. Puoi concatenarli per creare componibili più completi. Ne utilizzerai alcune per migliorare il layout.

// ...
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
mostra anteprima
nascondi anteprima
// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
mostra anteprima
nascondi anteprima
Anteprima di due testi componibili sovrapposti
// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

mostra anteprima
nascondi anteprima
// ...
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.res.painterResource

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
mostra anteprima
nascondi anteprima
// ...
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
mostra anteprima
nascondi anteprima

Lezione 3: Material Design

Compose è progettato per supportare i principi di Material Design. Molti degli elementi dell'interfaccia utente implementano subito il Material Design. In questa lezione imparerai a definire lo stile della tua app con i widget di Material Design.

Utilizza Material Design

Il design dei tuoi messaggi ora ha un layout, che però non ha ancora un aspetto fantastico.

Jetpack Compose fornisce un'implementazione immediata di Material Design 3 e dei suoi elementi UI. Migliorerai l'aspetto del nostro componibile MessageCard usando lo stile di Material Design.

Per iniziare, aggrega la funzione MessageCard con il tema Material creato nel tuo progetto, ComposeTutorialTheme, e un Surface. Esegui questa operazione sia nella funzione @Preview che nella funzione setContent. In questo modo i componibili potranno ereditare gli stili definiti nel tema della tua app, garantendo coerenza in tutta l'app.

Il Material Design si basa su tre pilastri: Color, Typography e Shape. Li aggiungerai uno alla volta.

Nota: il modello di attività di Scrittura vuoto genera un tema predefinito per il progetto che ti consente di personalizzare MaterialTheme. Se hai assegnato al tuo progetto un nome diverso da ComposeTutorial, puoi trovare il tema personalizzato nel file Theme.kt nel sottopacchetto ui.theme.

// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}


  
mostra anteprima
nascondi anteprima

Colore

Utilizza MaterialTheme.colorScheme per applicare uno stile con i colori del tema a capo. Puoi utilizzare questi valori del tema ovunque sia necessario un colore. In questo esempio vengono utilizzati colori dinamici per la tematizzazione (definiti in base alle preferenze del dispositivo). Puoi impostare dynamicColor su false nel file MaterialTheme.kt per modificare questa impostazione.

Applica uno stile al titolo e aggiungi un bordo all'immagine.

// ...
import androidx.compose.foundation.border
import androidx.compose.material3.MaterialTheme

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
mostra anteprima
nascondi anteprima

Tipografia

Gli stili di tipografia del materiale sono disponibili in MaterialTheme; devi solo aggiungerli agli elementi componibili Text.

// ...

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.bodyMedium
           )
       }
   }
}

  
mostra anteprima
nascondi anteprima

Forma

Con Shape puoi aggiungere gli ultimi ritocchi. Innanzitutto, aggrega il testo del corpo del messaggio intorno a un componibile Surface. In questo modo è possibile personalizzare la forma e l'elevazione del corpo del messaggio. Viene inoltre aggiunta una spaziatura interna al messaggio per migliorare il layout.

// ...
import androidx.compose.material3.Surface

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.bodyMedium
               )
           }
       }
   }
}

  
mostra anteprima
nascondi anteprima

Attiva tema scuro

Il tema scuro (o la modalità notturna) può essere attivato per evitare un display luminoso, soprattutto di notte, o semplicemente per risparmiare batteria. Grazie al supporto di Material Design, Jetpack Compose è in grado di gestire il tema scuro per impostazione predefinita. Dopo aver utilizzato i colori, il testo e gli sfondi di Material Design, si adattano automaticamente allo sfondo scuro.

Puoi creare più anteprime nel file come funzioni separate oppure aggiungere più annotazioni alla stessa funzione.

Aggiungi una nuova annotazione di anteprima e attiva la modalità notturna.

// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
mostra anteprima
nascondi anteprima

La scelta dei colori per i temi chiaro e scuro è definita nel file Theme.kt generato dall'IDE.

Finora, hai creato un elemento dell'interfaccia utente dei messaggi che mostra un'immagine e due testi con stili diversi e ha un ottimo aspetto sia con i temi chiaro che con quelli scuri.

// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
mostra anteprima
nascondi anteprima
// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}


  
mostra anteprima
nascondi anteprima
// ...
import androidx.compose.foundation.border
import androidx.compose.material3.MaterialTheme

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
mostra anteprima
nascondi anteprima
// ...

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.bodyMedium
           )
       }
   }
}

  
mostra anteprima
nascondi anteprima
// ...
import androidx.compose.material3.Surface

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.bodyMedium
               )
           }
       }
   }
}

  
mostra anteprima
nascondi anteprima
// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
mostra anteprima
nascondi anteprima
Anteprima che mostra componibili a tema chiaro e scuro.

Lezione 4: elenchi e animazioni

Elenchi e animazioni sono ovunque nelle app. In questa lezione scoprirai in che modo Compose semplifica la creazione di elenchi e rende divertente l'aggiunta di animazioni.

Creare un elenco di messaggi

Una chat con un solo messaggio sembra un po' solitaria, quindi cambieremo la conversazione in modo che contenga più di un messaggio. Devi creare una funzione Conversation che mostri più messaggi. Per questo caso d'uso, usa LazyColumn e LazyRow di Compose. Questi elementi componibili visualizzano solo gli elementi visibili sullo schermo, pertanto sono progettati per essere molto efficienti per elenchi lunghi.

In questo snippet di codice, puoi vedere che LazyColumn ha un elemento secondario items. Prende un List come parametro e il relativo lambda riceve un parametro che abbiamo chiamato message (avremmo potuto chiamarlo come vogliamo), che è un'istanza di Message. In breve, questo lambda viene chiamato per ogni elemento del valore List fornito. Copia il set di dati di esempio nel progetto per avviare rapidamente il bootstrap della conversazione.

// ...
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
mostra anteprima
nascondi anteprima

Animazione dei messaggi durante l'espansione

La conversazione sta diventando più interessante. È il momento di provare le animazioni! Potrai espandere un messaggio per mostrarne uno più lungo, animando sia le dimensioni dei contenuti sia il colore di sfondo. Per archiviare questo stato dell'interfaccia utente locale, devi monitorare se un messaggio è stato espanso o meno. Per tenere traccia di questo cambiamento di stato, devi utilizzare le funzioni remember e mutableStateOf.

Le funzioni componibili possono archiviare lo stato locale in memoria utilizzando remember e tenere traccia delle modifiche al valore passato a mutableStateOf. I componibili (e i relativi elementi secondari) che utilizzano questo stato verranno ridisegnati automaticamente quando il valore viene aggiornato. Questa procedura è chiamata ricomposizione.

Utilizzando le API di stato di Compose come remember e mutableStateOf, qualsiasi modifica allo stato aggiorna automaticamente l'UI.

Nota: devi aggiungere le seguenti importazioni per utilizzare correttamente la sintassi delle proprietà delegate di Kotlin (la parola chiave by). Alt+Invio o Opzione+Invio li aggiunge per te.
import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue

// ...
import androidx.compose.foundation.clickable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           ComposeTutorialTheme {
               Conversation(SampleData.conversationSample)
           }
       }
   }
}

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
mostra anteprima
nascondi anteprima

Ora puoi modificare lo sfondo dei contenuti dei messaggi in base a isExpanded quando facciamo clic su un messaggio. Utilizzerai il modificatore clickable per gestire gli eventi di clic nell'elemento componibile. Anziché attivare/disattivare solo il colore di sfondo di Surface, anima il colore di sfondo modificando gradualmente il suo valore da MaterialTheme.colorScheme.surface a MaterialTheme.colorScheme.primary e viceversa. Per farlo, utilizzerai la funzione animateColorAsState. Infine, utilizzerai il modificatore animateContentSize per animare le dimensioni del contenitore dei messaggi senza problemi:

// ...
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(1.dp)
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
mostra anteprima
nascondi anteprima
// ...
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
mostra anteprima
nascondi anteprima
// ...
import androidx.compose.foundation.clickable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           ComposeTutorialTheme {
               Conversation(SampleData.conversationSample)
           }
       }
   }
}

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
mostra anteprima
nascondi anteprima
// ...
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(1.dp)
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
mostra anteprima
nascondi anteprima

Passaggi successivi

Congratulazioni, hai terminato il tutorial su Scrivi. Hai creato una schermata della chat semplice che mostra in modo efficiente un elenco di messaggi espandibili e animati contenenti un'immagine e del testo, e il tutto in meno di 100 righe di codice.

Ecco cosa hai imparato finora:

  • Definizione delle funzioni componibili
  • Aggiunta di elementi diversi alla composizione
  • Strutturare il componente dell'interfaccia utente utilizzando i layout componibili
  • Estensione dei componibili mediante modificatori
  • Creazione di un elenco efficiente
  • Monitorare lo stato e modificarlo
  • Aggiunta dell'interazione utente a una creatività componibile
  • Animazione dei messaggi durante l'espansione

Se vuoi approfondire alcuni di questi passaggi, esplora le risorse di seguito.

Passaggi successivi

Configurazione
Ora che hai completato il tutorial su Scrivi, puoi iniziare a creare con Scrivi.
Viale
Dai un'occhiata al nostro percorso selezionato di codelab e video che ti aiuteranno a imparare e a padroneggiare Jetpack Compose.