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 dell'interfaccia utente su Android con meno codice, strumenti potenti e API Kotlin intuitive.

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

Anteprima completa
Anteprima completa

Lezione 1: funzioni componibili

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

Aggiungere 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à vuote. Assegna all'app il nome ComposeTutorial e fai clic su Finish (Fine). Il modello predefinito contiene già alcuni elementi di Compose, ma in questo tutorial ne occuperai la creazione passo passo.

Innanzitutto, visualizza il 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

Definisci una funzione componibile

Per rendere componibile una funzione, aggiungi l'annotazione @Composable. Per fare una prova, 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

Visualizza l'anteprima della funzione in Android Studio

L'annotazione @Preview ti 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 l'anteprima della funzione MessageCard. Crea invece una seconda funzione denominata PreviewMessageCard, che chiami 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 (design/codice). Questa finestra mostra un'anteprima degli elementi dell'interfaccia utente creati dalle funzioni componibili contrassegnate 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 Scrivi, crei una gerarchia dell'interfaccia utente chiamando funzioni componibili da altre funzioni componibili.

Aggiungere più testi

Finora hai creato la tua prima funzione componibile e l'anteprima. Per scoprire altre funzionalità di Jetpack Compose, creerai una semplice schermata di messaggi 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 il contenuto del messaggio. Devi prima modificare il parametro componibile in modo da accettare un oggetto Message anziché un String, quindi aggiungere un altro Text componibile all'interno del MessageCard componibile. 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 alcuna informazione 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 verticalmente. Aggiungi Column alla funzione MessageCard.
Puoi utilizzare Row per disporre gli elementi orizzontalmente e Box per sovrapporre 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

Aggiungere 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 o utilizza questa. Aggiungi un elemento componibile Row per avere un design ben strutturato e un elemento Image componibile 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 una composizione, Compose utilizza modificatori. Consentono di modificare le dimensioni, il layout e l'aspetto della componibile oppure di aggiungere interazioni di alto livello, ad esempio rendendo cliccabile un elemento. Puoi concatenarli per creare componibili più completi. Ne utilizzerai alcuni 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 dei suoi elementi dell'interfaccia utente implementano il Material Design da subito. In questa lezione, applicherai uno stile alla tua app con i widget di Material Design.

Utilizza il material design

Il design del tuo messaggio ora ha un layout, ma non ha ancora un aspetto ottimale.

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

Per iniziare, aggrega la funzione MessageCard al tema Material creato nel tuo progetto, ComposeTutorialTheme, nonché a Surface. Fallo sia nella funzione @Preview sia nella funzione setContent. In questo modo gli elementi componibili erediteranno gli stili definiti nel tema dell'app, garantendo coerenza all'interno dell'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 vuota genera un tema predefinito per il progetto che ti consente di personalizzare MaterialTheme. Se hai assegnato al progetto un nome diverso da ComposeTutorial, potrai 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

Usa MaterialTheme.colorScheme per applicare uno stile con i colori del tema a capo. Puoi utilizzare questi valori del tema ovunque sia necessario un colore. Questo esempio utilizza i colori dei temi dinamici (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 Material Tipografia 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, racchiudi il testo del corpo del messaggio intorno a un elemento componibile Surface. In questo modo è possibile personalizzare la forma e l'elevazione del corpo del messaggio. Viene aggiunta anche 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. L'utilizzo dei colori, del testo e degli sfondi di Material Design si adatta 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 sono definite nel file Theme.kt generato dall'IDE.

Finora hai creato un elemento UI per i messaggi che mostra un'immagine e due testi con stili diversi e che si adatta perfettamente sia ai temi chiari sia a 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 disponibili 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

Chattare con un solo messaggio sembra un po' sola, quindi cambieremo la conversazione in modo che contenga più di un messaggio. Dovrai creare una funzione Conversation che mostri più messaggi. Per questo caso d'uso, utilizza LazyColumn e LazyRow di Compose. Questi elementi componibili visualizzano solo gli elementi visibili sullo schermo, pertanto sono progettati per essere molto efficienti per gli 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 denominato message (potremmo assegnarlo come vogliamo), che è un'istanza di Message. In breve, questo lambda viene chiamato per ogni elemento dell'elemento List fornito. Copia il set di dati di esempio nel tuo progetto per eseguire 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 giocare con le animazioni! Aggiungerai la possibilità di 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 monitorare 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 figlio) che utilizzano questo stato verranno ridisegnati automaticamente quando il valore viene aggiornato. Questa operazione è 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 le 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 del messaggio in base a isExpanded quando facciamo clic su un messaggio. Utilizzerai il modificatore clickable per gestire gli eventi di clic nella composizione. Anziché modificare semplicemente il colore di sfondo di Surface, puoi animare il colore di sfondo modificandolo gradualmente da MaterialTheme.colorScheme.surface a MaterialTheme.colorScheme.primary e viceversa. A tale scopo, 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.