Lekcja 1. Funkcje kompozycyjne
Jetpack Compose opiera się na funkcjach kompozycyjnych. Te funkcje umożliwiają automatyczne zdefiniowanie interfejsu aplikacji przez opisanie, jak powinien wyglądać i podanie zależności danych. Dzięki temu nie musisz skupiać się na procesie tworzenia interfejsu (inicjowaniu elementu, dołączaniu go do elementu nadrzędnego itd.). Aby utworzyć funkcję kompozycyjną, dodaj do jej nazwy adnotację @Composable
.
Dodaj element tekstowy
Aby rozpocząć, pobierz najnowszą wersję Android Studio i utwórz aplikację, wybierając Nowy projekt, a następnie w kategorii Telefon i tablet wybierz Pusta aktywność. Nazwij swoją aplikację Composetutorial i kliknij Finish (Zakończ). Domyślny szablon zawiera już elementy tworzenia wiadomości, ale w tym samouczku utworzysz go krok po kroku.
Najpierw wyświetl tekst „Hello world!”, dodając element tekstowy w metodzie onCreate
. W tym celu musisz zdefiniować blok treści i wywołać funkcję kompozycyjną
Text
. Blok setContent
określa układ aktywności, w którym wywoływane są funkcje kompozycyjne. Funkcje kompozycyjne można wywoływać tylko z innych funkcji kompozycyjnych.
Jetpack Compose używa wtyczki kompilatora Kotlin do przekształcenia tych funkcji kompozycyjnych w elementy interfejsu aplikacji. Na przykład funkcja kompozycyjna Text
, która jest zdefiniowana przez bibliotekę interfejsu tworzenia, wyświetla etykietę tekstową na ekranie.
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!") } } }
Zdefiniuj funkcję kompozycyjną
Aby funkcja była kompozycyjna, dodaj adnotację @Composable
.
Aby wypróbować tę funkcję, zdefiniuj funkcję MessageCard
, która będzie przekazywać nazwę, i użyj jej do konfigurowania elementu tekstowego.
// ... 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!") }
Wyświetl podgląd funkcji w Android Studio
Adnotacja @Preview
umożliwia wyświetlenie podglądu funkcji kompozycyjnych w Android Studio bez konieczności kompilowania i instalowania aplikacji na urządzeniu z Androidem lub emulatorze. Adnotacji należy używać w funkcji kompozycyjnej, która nie przyjmuje parametrów. Z tego powodu nie możesz bezpośrednio wyświetlić podglądu funkcji MessageCard
. Zamiast tego utwórz drugą funkcję o nazwie PreviewMessageCard
, która wywołuje funkcję MessageCard
z odpowiednim parametrem. Dodaj adnotację @Preview
przed obiektem @Composable
.
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }
Przebuduj projekt. Aplikacja nie ulega zmianie, ponieważ nowa funkcja PreviewMessageCard
nie jest nigdzie wywoływana, ale Android Studio dodaje okno podglądu, które można rozwinąć, klikając widok podzielony (projekt/kod). To okno wyświetla podgląd elementów interfejsu utworzonych przez funkcje kompozycyjne oznaczone adnotacją @Preview
. Podgląd możesz zaktualizować w dowolnym momencie, klikając przycisk odświeżania u góry okna podglądu.
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") }
Lekcja 2. Układy
Elementy interfejsu są hierarchiczne – zawierają też inne elementy. W usłudze Compose tworzysz hierarchię interfejsu, wywołując funkcje kompozycyjne z innych funkcji kompozycyjnych.
Dodawanie wielu tekstów
Masz już pierwszą funkcję kompozycyjną i podgląd. Aby odkryć więcej możliwości Jetpack Compose, musisz utworzyć prosty ekran przesyłania wiadomości zawierający listę wiadomości, które można rozwinąć za pomocą niektórych animacji.
Zacznij od wzbogacenia wiadomości kompozycyjnej przez wyświetlenie nazwy jej autora oraz treści wiadomości. Musisz najpierw zmienić parametr kompozycyjny, by akceptował obiekt Message
zamiast String
, i dodać kolejny element kompozycyjny Text
w funkcji kompozycyjnej MessageCard
. Pamiętaj, aby zaktualizować też podgląd.
// ... 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!") ) }
Ten kod tworzy w widoku treści dwa elementy tekstowe. Ponieważ jednak nie podano żadnych informacji o tym, jak je rozmieścić, elementy tekstowe są rysowane jeden na drugim, przez co tekst jest nieczytelny.
Korzystanie z kolumny
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }
Dodaj element graficzny
Uzupełnij kartę wiadomości, dodając zdjęcie profilowe nadawcy. Użyj Menedżera zasobów, aby zaimportować obraz z biblioteki zdjęć, lub tego. Dodaj funkcję Row
kompozycyjną, aby miała prawidłową strukturę, a w niej element kompozycyjny
Image
.
// ... 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) } } }
Konfigurowanie układu
Układ wiadomości ma odpowiednią strukturę, ale jego elementy nie są odpowiednio rozmieszczone, a obraz jest za duży. Do udekorowania lub skonfigurowania funkcji kompozycyjnej w widoku Compose używane są modyfikatory. Pozwalają one zmieniać rozmiar, układ i wygląd elementu kompozycyjnego oraz dodawać interakcje ogólne, takie jak elementy, które można kliknąć. Możesz łączyć je w łańcuchy, aby tworzyć bogatsze elementy kompozycyjne. Użyjesz ich do ulepszenia układu.
// ... 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) } } }
Lekcja 3. Material Design
Funkcja tworzenia wiadomości jest zgodna z zasadami Material Design. Wiele elementów interfejsu od razu korzysta z interfejsu Material Design. Podczas tej lekcji dostosujesz styl swojej aplikacji za pomocą widżetów Material Design.
Użyj stylu Material Design
Twój projekt wiadomości ma teraz układ, ale jeszcze nie wygląda najlepiej.
Jetpack Compose udostępnia od razu implementację Material Design 3 i jego elementów interfejsu. Poprawisz wygląd naszego elementu kompozycyjnego MessageCard
za pomocą stylu Material Design.
Zacznij od opakowania funkcji MessageCard
motywem Material Design utworzonym w projekcie ComposeTutorialTheme
oraz z Surface
.
Zrób to zarówno w funkcji @Preview
, jak i w funkcji setContent
. Umożliwi to elementom kompozycyjnym dziedziczenie stylów zdefiniowanych w motywie aplikacji, co zapewni spójność w całej aplikacji.
Material Design opiera się na 3 filarach: Color
, Typography
i Shape
.
Będziesz dodawać je pojedynczo.
Uwaga: szablon działania pustego tworzenia wiadomości generuje domyślny motyw dla projektu, który pozwala dostosować
MaterialTheme
.
Jeśli projekt ma inną nazwę niż Composetutorial, motyw niestandardowy znajdziesz w pliku Theme.kt
w podpakiecie 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!") ) } } }
Kolor
Użyj elementu MaterialTheme.colorScheme
, aby dostosować styl za pomocą kolorów z opakowanego motywu. Możesz użyć tych wartości z motywu wszędzie tam, gdzie potrzebujesz koloru. W tym przykładzie zastosowano dynamiczne kolory motywów (określone przez ustawienia urządzenia).
Aby to zmienić, możesz ustawić dynamicColor
na false
w pliku MaterialTheme.kt
.
Zmień styl tytułu i dodaj obramowanie obrazu.
// ... 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) } } }
Typografia
Style typografii materiału są dostępne tutaj: MaterialTheme
. Wystarczy, że dodasz je do funkcji kompozycyjnych 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 ) } } }
Kształt
Dzięki Shape
możesz wprowadzić ostatnie poprawki. Najpierw opakuj tekst wiadomości wokół funkcji kompozycyjnej
Surface
. Pozwoli Ci to dostosować kształt i wysokość treści wiadomości. Do wiadomości zostaje też dodane dopełnienie, które zapewnia lepszy układ.
// ... 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 ) } } } }
Włącz ciemny motyw
Możesz włączyć ciemny motyw (lub tryb nocny), aby ograniczyć jasność ekranu w nocy lub po prostu oszczędzać baterię. Dzięki obsłudze Material Design Jetpack Compose domyślnie może obsługiwać ciemny motyw. Jeśli użyjesz kolorów w stylu Material Design, tekst i tło będą automatycznie dostosowywać się do ciemnego tła.
Możesz utworzyć wiele podglądów w pliku jako osobne funkcje lub dodać wiele adnotacji do tej samej funkcji.
Dodaj nową adnotację do podglądu i włącz tryb nocny.
// ... 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!") ) } } }
Wybór kolorów dla motywów jasnych i ciemnych jest określony w pliku Theme.kt
wygenerowanym przez IDE.
Do tej pory udało Ci się utworzyć element interfejsu wiadomości, który zawiera obraz i 2 teksty w różnych stylach. Ten element wygląda dobrze zarówno w jasnym, jak i ciemnym motywie.
// ... 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!") ) } } }
Lekcja 4. Listy i animacje
Listy i animacje są wszędzie w aplikacjach. Podczas tej lekcji dowiesz się, jak łatwo tworzyć listy i dodawać animacje za pomocą funkcji Utwórz.
Tworzenie listy wiadomości
Czat z 1 wiadomością czuje się trochę samotny, dlatego zmienimy rozmowę tak, żeby zawierała więcej niż 1 wiadomość. Musisz utworzyć funkcję Conversation
, która będzie wyświetlać wiele wiadomości. W tym przypadku użyj poleceń
LazyColumn
i
LazyRow
w sekcji Compose. Te funkcje kompozycyjne renderują tylko elementy widoczne na ekranie, więc są bardzo skuteczne w przypadku długich list.
W tym fragmencie kodu widać, że LazyColumn
ma element podrzędny items
. Wykorzystuje on parametr List
, a jego lambda otrzymuje parametr o nazwie message
(mogliśmy nadać mu dowolną nazwę), który jest wystąpieniem funkcji Message
.
Krótko mówiąc, ta funkcja jest wywoływana w przypadku każdego elementu w parametrze List
. Aby przyspieszyć rozruch rozmowy, skopiuj przykładowy zbiór danych do projektu.
// ... 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) } }
Animuj wiadomości przy rozwijaniu
Rozmowa staje się coraz bardziej interesująca. Czas na zabawę z animacjami. Dodasz możliwość rozwinięcia wiadomości, aby wyświetlić dłuższą wiadomość z animacją rozmiaru treści i koloru tła. Aby zapisać ten stan interfejsu lokalnego, musisz śledzić, czy wiadomość została rozwinięta. Aby śledzić zmianę stanu, musisz używać funkcji remember
i mutableStateOf
.
Funkcje kompozycyjne mogą przechowywać lokalny stan w pamięci za pomocą funkcji remember
i śledzić zmiany wartości przekazywanej do mutableStateOf
. Funkcje kompozycyjne (i ich elementy podrzędne) korzystające z tego stanu zostaną automatycznie ponownie wyświetlone po zaktualizowaniu wartości. Jest to tzw. rekompozycja.
Używanie interfejsów API stanu w Compose, takich jak remember
i mutableStateOf
, powoduje automatyczną aktualizację interfejsu.
Uwaga: aby prawidłowo korzystać ze składni właściwości delegowanej Kotlin (słowo kluczowe by
), musisz dodać te importy. Alt+Enter lub Option+Enter doda je za Ciebie.
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 ) } } } }
Teraz możesz zmienić tło treści wiadomości w zależności od tego, czy klikniemy element isExpanded
. Do obsługi zdarzeń kliknięcia w elemencie kompozycyjnym używasz modyfikatora clickable
. Zamiast zmieniać kolor tła obiektu Surface
, możesz utworzyć jego animację, stopniowo modyfikując jego wartość z MaterialTheme.colorScheme.surface
na MaterialTheme.colorScheme.primary
i odwrotnie. Użyjesz do tego funkcji animateColorAsState
. Na koniec użyj modyfikatora animateContentSize
, aby płynnie animować rozmiar kontenera wiadomości:
// ... 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 ) } } } }
Dalsze kroki
Gratulujemy! Samouczek dotyczący tworzenia wiadomości został ukończony. Udało Ci się stworzyć prosty ekran czatu, który efektywnie wyświetla listę rozwijanych i animowanych wiadomości zawierających obrazy oraz tekst. Zaprojektowano ją zgodnie z zasadami Material Design z ciemnym motywem i podglądami – a wszystko to w mniej niż 100 wierszach kodu.
Oto czego już się dowiedziałeś(-aś):
- Definiowanie funkcji kompozycyjnych
- Dodawanie różnych elementów do funkcji kompozycyjnej
- Tworzenie struktury komponentu interfejsu za pomocą funkcji kompozycyjnych układu
- Rozszerzanie funkcji kompozycyjnych za pomocą modyfikatorów
- Tworzenie skutecznej listy
- Śledzenie stanu i modyfikowanie go
- Dodawanie interakcji użytkownika do funkcji kompozycyjnej
- Animowanie wiadomości przy ich rozwijaniu
Jeśli chcesz dowiedzieć się więcej o niektórych z poniższych etapów, zapoznaj się z materiałami poniżej.