Учебное пособие

Руководство по составлению Jetpack

Jetpack Compose — это современный набор инструментов для создания собственного пользовательского интерфейса Android. Jetpack Compose упрощает и ускоряет разработку пользовательского интерфейса на Android за счет меньшего количества кода, мощных инструментов и интуитивно понятных API-интерфейсов Kotlin.

В этом руководстве вы создадите простой компонент пользовательского интерфейса с декларативными функциями. Вы не будете редактировать макеты XML или использовать редактор макетов. Вместо этого вы будете вызывать составные функции, чтобы определить, какие элементы вам нужны, а компилятор Compose сделает все остальное.

Полный предварительный просмотр
Полный предварительный просмотр

Добавить текстовый элемент

Для начала загрузите самую последнюю версию 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 . Чтобы обновить предварительный просмотр в любое время, нажмите кнопку обновления в верхней части окна предварительного просмотра.

Предварительный просмотр составной функции в Android Studio
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
показать предварительный просмотр
скрыть предварительный просмотр
// ...
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")
}
  
показать предварительный просмотр
скрыть предварительный просмотр
Предварительный просмотр компонуемой функции в Android Studio

Добавить несколько текстов

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

  
показать предварительный просмотр
скрыть предварительный просмотр

Этот код создает два текстовых элемента внутри представления содержимого. Однако, поскольку вы не предоставили никакой информации о том, как их расположить, текстовые элементы рисуются друг над другом, что делает текст нечитаемым.

Использование столбца

Функция Column позволяет расположить элементы вертикально. Добавьте Column в функцию MessageCard .
Вы можете использовать Row для горизонтального расположения элементов и Box для штабелирования элементов.

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

  
показать предварительный просмотр
скрыть предварительный просмотр
Предварительный просмотр двух перекрывающихся компонуемых объектов Text
// ...
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 строках кода!

Вот что вы узнали на данный момент:

  • Определение составных функций
  • Добавление различных элементов в композицию
  • Структурирование вашего компонента пользовательского интерфейса с использованием составных макетов
  • Расширение составных объектов с помощью модификаторов
  • Создание эффективного списка
  • Отслеживание состояния и его изменение
  • Добавление взаимодействия с пользователем в составной элемент
  • Анимация сообщений при их расширении

Если вы хотите углубиться в некоторые из этих шагов, изучите ресурсы ниже.

Следующие шаги

Настройка
Теперь, когда вы закончили обучение Compose, вы готовы приступить к созданию с помощью Compose.
Путь
Ознакомьтесь с нашим набором учебных пособий и видеороликов, которые помогут вам изучить и освоить Jetpack Compose.