Добавить текстовый элемент
Для начала загрузите самую последнюю версию 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!")
}
}
}

Определите составную функцию
Чтобы сделать функцию составной, добавьте аннотацию @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!")
}

Предварительный просмотр вашей функции в 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")
}

Перестройте свой проект. Само приложение не меняется, поскольку новая функция PreviewMessageCard
нигде не вызывается, но Android Studio добавляет окно предварительного просмотра, которое можно развернуть, щелкнув разделенное представление (дизайн/код). В этом окне показан предварительный просмотр элементов пользовательского интерфейса, созданных составными функциями, отмеченными аннотацией @Preview
. Чтобы обновить предварительный просмотр в любое время, нажмите кнопку обновления в верхней части окна предварительного просмотра.

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")
}


Добавить несколько текстов
Итак, вы создали свою первую составную функцию и предварительный просмотр! Чтобы узнать больше о возможностях 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!")
)
}

Этот код создает два текстовых элемента внутри представления содержимого. Однако, поскольку вы не предоставили никакой информации о том, как их расположить, текстовые элементы рисуются друг над другом, что делает текст нечитаемым.
// ...
import androidx.compose.foundation.layout.Column
@Composable
fun MessageCard(msg: Message) {
Column {
Text(text = msg.author)
Text(text = msg.body)
}
}

Добавьте элемент изображения
Расширьте свою карточку сообщения, добавив изображение профиля отправителя. Используйте диспетчер ресурсов , чтобы импортировать изображение из вашей библиотеки фотографий, или воспользуйтесь этим . Добавьте составную 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)
}
}
}

Настройте свой макет
Макет вашего сообщения имеет правильную структуру, но его элементы расположены неправильно, а изображение слишком велико! Чтобы украсить или настроить составной элемент, 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)
}
}
}

// ...
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)
}
}
}

Используйте материальный дизайн
У вашего сообщения теперь есть макет, но он пока выглядит не очень хорошо.
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!")
)
}
}
}

Цвет
Используйте 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)
}
}
}

Типография
Стили 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
)
}
}
}

Форма
С помощью 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
)
}
}
}
}

Включить темную тему
Темную тему (или ночной режим) можно включить, чтобы избежать яркого дисплея, особенно ночью, или просто для экономии заряда батареи устройства. Благодаря поддержке 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!")
)
}
}
}

Выбор цвета для светлых и темных тем определяется в файле 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!")
)
}
}
}

// ...
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!")
)
}
}
}


Создайте список сообщений
Чат с одним сообщением кажется немного одиноким, поэтому мы собираемся изменить диалог, чтобы в нем было более одного сообщения. Вам нужно будет создать функцию 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)
}
}

Анимация сообщений при расширении
Разговор становится интереснее. Пришло время поиграть с анимацией! Вы добавите возможность расширять сообщение, чтобы оно отображалось более длинное, анимируя как размер содержимого, так и цвет фона. Чтобы сохранить это локальное состояние пользовательского интерфейса, вам необходимо отслеживать, было ли сообщение развернуто или нет. Чтобы отслеживать это изменение состояния, вам нужно использовать функции 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)
}
}

// ...
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 строках кода!
Вот что вы узнали на данный момент:
- Определение составных функций
- Добавление различных элементов в композицию
- Структурирование вашего компонента пользовательского интерфейса с использованием составных макетов
- Расширение составных объектов с помощью модификаторов
- Создание эффективного списка
- Отслеживание состояния и его изменение
- Добавление взаимодействия с пользователем в составной элемент
- Анимация сообщений при их расширении
Если вы хотите углубиться в некоторые из этих шагов, изучите ресурсы ниже.