Добавить текстовый элемент
Для начала загрузите самую последнюю версию Android Studio и создайте приложение, выбрав «Новый проект» и в категории «Телефон и планшет» выберите «Пустое действие» . Назовите свое приложение ComposeTutorial и нажмите «Готово» . Шаблон по умолчанию уже содержит некоторые элементы Compose, но в этом руководстве вы создадите его шаг за шагом.
Сначала отобразите «Привет, мир!» text, добавив текстовый элемент внутри метода onCreate
. Это можно сделать, определив блок контента и вызвав функцию Text
Composable. Блок setContent
определяет макет активности, в которой вызываются составные функции. Составные функции можно вызывать только из других составных функций.
Jetpack Compose использует плагин компилятора Kotlin для преобразования этих компонуемых функций в элементы пользовательского интерфейса приложения. Например, функция компоновки Text
, определенная библиотекой Compose UI, отображает на экране текстовую метку.
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!") } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson1-02.png?authuser=0000&hl=ru)
Определите составную функцию
Чтобы сделать функцию составной, добавьте аннотацию @Composable
. Чтобы попробовать это, определите функцию MessageCard
, которой передается имя и которая использует его для настройки текстового элемента.
// ... 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!") }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson1-03.png?authuser=0000&hl=ru)
Предварительный просмотр вашей функции в Android Studio
Аннотация @Preview
позволяет предварительно просмотреть компонуемые функции в Android Studio без необходимости сборки и установки приложения на устройство или эмулятор Android. Аннотация должна использоваться для составной функции, которая не принимает параметры. По этой причине вы не можете просмотреть функцию MessageCard
напрямую. Вместо этого создайте вторую функцию с именем PreviewMessageCard
, которая вызывает MessageCard
с соответствующим параметром. Добавьте аннотацию @Preview
перед @Composable
.
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson1-03.png?authuser=0000&hl=ru)
Перестройте свой проект. Само приложение не меняется, поскольку новая функция PreviewMessageCard
нигде не вызывается, но Android Studio добавляет окно предварительного просмотра, которое можно развернуть, щелкнув разделенное представление (дизайн/код). В этом окне показан предварительный просмотр элементов пользовательского интерфейса, созданных составными функциями, отмеченными аннотацией @Preview
. Чтобы обновить предварительный просмотр в любое время, нажмите кнопку обновления в верхней части окна предварительного просмотра.
![Предварительный просмотр составной функции в Android Studio](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson1-04.png?authuser=0000&hl=ru)
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!") } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson1-02.png?authuser=0000&hl=ru)
// ... 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!") }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson1-03.png?authuser=0000&hl=ru)
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson1-03.png?authuser=0000&hl=ru)
![Предварительный просмотр компонуемой функции в Android Studio](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson1-04.png?authuser=0000&hl=ru)
Добавить несколько текстов
Итак, вы создали свою первую составную функцию и предварительный просмотр! Чтобы узнать больше о возможностях Jetpack Compose, вы создадите простой экран сообщений, содержащий список сообщений, который можно расширить с помощью анимации.
Начните с того, что сделайте сообщение богаче, отображая имя его автора и содержание сообщения. Сначала вам необходимо изменить составной параметр, чтобы он принимал объект Message
вместо String
, и добавить еще один составной элемент Text
внутри составного объекта MessageCard
. Обязательно обновите предварительный просмотр.
// ... 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!") ) }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson2-02.png?authuser=0000&hl=ru)
Этот код создает два текстовых элемента внутри представления содержимого. Однако, поскольку вы не предоставили никакой информации о том, как их расположить, текстовые элементы рисуются друг над другом, что делает текст нечитаемым.
Использование столбца
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson2-04.png?authuser=0000&hl=ru)
Добавьте элемент изображения
Расширьте свою карточку сообщения, добавив изображение профиля отправителя. Используйте диспетчер ресурсов , чтобы импортировать изображение из вашей библиотеки фотографий, или воспользуйтесь этим . Добавьте составную Row
, чтобы иметь хорошо структурированный дизайн, и 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) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson2-05.png?authuser=0000&hl=ru)
Настройте свой макет
Макет вашего сообщения имеет правильную структуру, но его элементы расположены неправильно, а изображение слишком велико! Чтобы украсить или настроить составной элемент, Compose использует модификаторы . Они позволяют вам изменить размер, макет, внешний вид составного элемента или добавить высокоуровневые взаимодействия, например сделать элемент кликабельным. Вы можете объединить их в цепочку, чтобы создать более богатые составные элементы. Некоторые из них вы будете использовать для улучшения макета.
// ... 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) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson2-06.png?authuser=0000&hl=ru)
// ... 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!") ) }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson2-02.png?authuser=0000&hl=ru)
![Предварительный просмотр двух перекрывающихся компонуемых объектов Text](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson2-03.png?authuser=0000&hl=ru)
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson2-04.png?authuser=0000&hl=ru)
// ... 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) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson2-05.png?authuser=0000&hl=ru)
// ... 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) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson2-06.png?authuser=0000&hl=ru)
Используйте материальный дизайн
У вашего сообщения теперь есть макет, но он пока выглядит не очень хорошо.
Jetpack Compose предоставляет реализацию Material Design 3 и его элементов пользовательского интерфейса «из коробки». Вы улучшите внешний вид нашей составной MessageCard
, используя стиль Material Design.
Для начала оберните функцию MessageCard
темой Material, созданной в вашем проекте, ComposeTutorialTheme
, а также Surface
. Сделайте это как в @Preview
, так и в функции setContent
. Это позволит вашим составным элементам наследовать стили, определенные в теме вашего приложения, обеспечивая согласованность во всем приложении.
Материальный дизайн построен на трех столпах: Color
, Typography
и Shape
. Вы будете добавлять их один за другим.
Примечание. Шаблон Empty Compose Activity создает тему по умолчанию для вашего проекта, которая позволяет вам настраивать MaterialTheme
. Если вы назвали свой проект иначе, чем ComposeTutorial , вы можете найти свою собственную тему в файле Theme.kt
в подпакете 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!") ) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-02.png?authuser=0000&hl=ru)
Цвет
Используйте MaterialTheme.colorScheme
для стилизации с использованием цветов из завернутой темы. Вы можете использовать эти значения из темы везде, где требуется цвет. В этом примере используются цвета динамической темы (определяемые настройками устройства). Чтобы изменить это, вы можете установить для dynamicColor
значение false
в файле MaterialTheme.kt
.
Оформите заголовок и добавьте рамку к изображению.
// ... 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) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-03.png?authuser=0000&hl=ru)
Типография
Стили Material Typography доступны в MaterialTheme
, просто добавьте их в составные элементы 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 ) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-04.png?authuser=0000&hl=ru)
Форма
С помощью Shape
вы можете добавить последние штрихи. Сначала оберните текст сообщения вокруг составного элемента Surface
. Это позволит настроить форму и высоту тела сообщения. К сообщению также добавляется отступ для лучшего макета.
// ... 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 ) } } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-05.png?authuser=0000&hl=ru)
Включить темную тему
Темную тему (или ночной режим) можно включить, чтобы избежать яркого дисплея, особенно ночью, или просто для экономии заряда батареи устройства. Благодаря поддержке Material Design Jetpack Compose по умолчанию поддерживает темную тему. При использовании цветов Material Design текст и фон автоматически адаптируются к темному фону.
Вы можете создать несколько предварительных просмотров в файле как отдельные функции или добавить несколько аннотаций к одной и той же функции.
Добавьте новую аннотацию предварительного просмотра и включите ночной режим.
// ... 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!") ) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-06.png?authuser=0000&hl=ru)
Выбор цвета для светлых и темных тем определяется в файле Theme.kt
, созданном IDE.
На данный момент вы создали элемент пользовательского интерфейса сообщения, который отображает изображение и два текста в разных стилях и хорошо смотрится как в светлой, так и в темной темах!
// ... 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!") ) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-06.png?authuser=0000&hl=ru)
// ... 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!") ) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-02.png?authuser=0000&hl=ru)
// ... 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) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-03.png?authuser=0000&hl=ru)
// ... @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 ) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-04.png?authuser=0000&hl=ru)
// ... 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 ) } } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-05.png?authuser=0000&hl=ru)
// ... 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!") ) } } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-06.png?authuser=0000&hl=ru)
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson3-07.png?authuser=0000&hl=ru)
Создайте список сообщений
Чат с одним сообщением кажется немного одиноким, поэтому мы собираемся изменить диалог, чтобы в нем было более одного сообщения. Вам нужно будет создать функцию Conversation
, которая будет отображать несколько сообщений. В этом случае используйте LazyColumn
и LazyRow
Compose. Эти составные элементы визуализируют только те элементы, которые видны на экране, поэтому они разработаны так, чтобы быть очень эффективными для длинных списков.
В этом фрагменте кода вы можете видеть, что LazyColumn
есть дочерний items
. Он принимает List
в качестве параметра, а его лямбда-выражение получает параметр, который мы назвали message
(мы могли бы назвать его как угодно), который является экземпляром Message
. Короче говоря, эта лямбда вызывается для каждого элемента предоставленного List
. Скопируйте образец набора данных в свой проект, чтобы быстро начать разговор.
// ... 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) } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson4-02.png?authuser=0000&hl=ru)
Анимация сообщений при расширении
Разговор становится интереснее. Пришло время поиграть с анимацией! Вы добавите возможность расширять сообщение, чтобы оно отображалось более длинное, анимируя как размер содержимого, так и цвет фона. Чтобы сохранить это локальное состояние пользовательского интерфейса, вам необходимо отслеживать, было ли сообщение развернуто или нет. Чтобы отслеживать это изменение состояния, вам нужно использовать функции remember
и mutableStateOf
.
Компонуемые функции могут хранить локальное состояние в памяти с помощью remember
и отслеживать изменения значения, переданного в mutableStateOf
. Составные объекты (и их дочерние элементы), использующие это состояние, будут автоматически перерисовываться при обновлении значения. Это называется рекомпозиция .
Используя API-интерфейсы состояния Compose, такие как remember
и mutableStateOf
, любые изменения состояния автоматически обновляют пользовательский интерфейс.
Примечание. Чтобы правильно использовать синтаксис делегированных свойств Kotlin (ключевое слово by
), необходимо добавить следующие элементы импорта. Alt+Enter или Option+Enter добавит их за вас.
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 ) } } } }
Теперь вы можете изменить фон содержимого сообщения на основе isExpanded
когда мы нажимаем на сообщение. Вы будете использовать модификатор clickable
для обработки событий щелчка на составном объекте. Вместо простого переключения цвета фона Surface
вы анимируете цвет фона, постепенно изменяя его значение с MaterialTheme.colorScheme.surface
на MaterialTheme.colorScheme.primary
и наоборот. Для этого вы будете использовать функцию animateColorAsState
. Наконец, вы будете использовать модификатор animateContentSize
для плавной анимации размера контейнера сообщения:
// ... 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) } }
![](https://developer.android.com/static/develop/ui/compose/images/compose-tutorial/lesson4-02.png?authuser=0000&hl=ru)
// ... 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 ) } } } }
Следующие шаги
Поздравляем, вы завершили урок по созданию текста! Вы создали простой экран чата, эффективно показывающий список расширяемых и анимированных сообщений, содержащих изображение и текст, разработанный с использованием принципов Material Design с включенной темной темой и предварительным просмотром — и все это менее чем в 100 строках кода!
Вот что вы узнали на данный момент:
- Определение составных функций
- Добавление различных элементов в композицию
- Структурирование вашего компонента пользовательского интерфейса с использованием составных макетов
- Расширение составных объектов с помощью модификаторов
- Создание эффективного списка
- Отслеживание состояния и его изменение
- Добавление взаимодействия с пользователем в составной элемент
- Анимация сообщений при их расширении
Если вы хотите углубиться в некоторые из этих шагов, изучите ресурсы ниже.