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!") } } }
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!") }
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") }
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.
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!") } } }
// ... 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!") }
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }
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!") ) }
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
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }
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) } } }
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) } } }
// ... 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!") ) }
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }
// ... 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) } } }
// ... 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) } } }
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!") ) } } }
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) } } }
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 ) } } }
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 ) } } } }
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!") ) } } }
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!") ) } } }
// ... 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!") ) } } }
// ... 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) } } }
// ... @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 ) } } }
// ... 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 ) } } } }
// ... 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!") ) } } }
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) } }
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 ) } } } }
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 ) } } } }
// ... 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) } }
// ... 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 ) } } } }
// ... 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 ) } } } }
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.