Урок 4 из 8 · 25 мин · обновлено

Экран на Jetpack Compose: текст, кнопки и списки

Как устроены composable-функции, как раскладывать элементы через Column и Row, настраивать их модификаторами, показывать длинные списки через LazyColumn и смотреть экран в Preview.

Интерфейс как функции

В Jetpack Compose экран описывают кодом. Каждый кусок интерфейса — composable-функция с пометкой @Composable:

Kotlin
@Composable
fun ProductRow(name: String, count: Int) {
    Text(text = "$name — $count шт.")
}

Text — готовая composable-функция из библиотеки Material 3. Есть и другие: Button, TextField, Image, Checkbox, Card, Icon. Свои функции собирают из них, как из кирпичиков, а экран целиком — из своих функций.

Названия composable-функций пишут с заглавной буквы, как названия классов: так их легко отличить от обычных функций.

Column, Row и Box

Элементы раскладывают контейнерами:

  • Column — друг под другом;
  • Row — в ряд слева направо;
  • Box — друг поверх друга, например значок поверх картинки.
Kotlin
@Composable
fun ProductRow(name: String, count: Int, onDelete: () -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Column(modifier = Modifier.weight(1f)) {
            Text(text = name, style = MaterialTheme.typography.titleMedium)
            Text(text = "$count шт.", style = MaterialTheme.typography.bodySmall)
        }
        Button(onClick = onDelete) {
            Text("Удалить")
        }
    }
}

Строка товара: слева колонка из названия и количества, справа кнопка.

  • onDelete: () -> Unit — параметр-функция: что делать при нажатии. Строка сама не решает, как удалять товар, — это решит экран, который её использует;
  • Button(onClick = …) — кнопка, а внутри неё в фигурных скобках — её содержимое, текст;
  • MaterialTheme.typography — готовые стили текста из темы приложения.

Modifier

Модификаторы настраивают элемент: размер, отступы, фон, рамку, реакцию на нажатия. Их пишут цепочкой, и порядок важен:

  • fillMaxWidth() — растянуть на всю ширину;
  • padding(16.dp) — отступ 16 dp;
  • weight(1f) в Row — занять всё свободное место, оттеснив кнопку вправо;
  • background(Color.LightGray) — фон;
  • clickable { … } — сделать элемент нажимаемым.

dp — единица размера, которая выглядит одинаково на экранах с разной плотностью пикселей. Для шрифтов используют похожую единицу sp: она учитывает размер шрифта, выбранный в настройках телефона.

Списки и Preview

Список покупок может быть длинным. Для таких списков есть LazyColumn: он создаёт только строки, видимые на экране, и подгружает остальные при прокрутке.

Kotlin
@Composable
fun ShoppingList(products: List<Product>) {
    LazyColumn {
        items(products) { product ->
            ProductRow(
                name = product.name,
                count = product.count,
                onDelete = { },
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun ShoppingListPreview() {
    ShoppingList(
        listOf(Product("Молоко", 2), Product("Хлеб", 1), Product("Яблоки", 6))
    )
}
  • items(products) { product -> … } — для каждого товара нарисовать строку;
  • onDelete = { } — пока кнопка ничего не делает, удаление добавим в следующем уроке;
  • @Preview — Android Studio покажет этот экран в панели справа, без запуска эмулятора. Удобно подбирать отступы и стили.

Попробуйте сами. Добавьте над списком заголовок «Список покупок» крупным шрифтом, а под ним — строку «Всего позиций: 3». Используйте Column, внутри которого заголовок, строка с количеством и LazyColumn.

Решение
Kotlin
@Composable
fun ShoppingScreen(products: List<Product>) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Список покупок",
            style = MaterialTheme.typography.headlineMedium,
        )
        Text(text = "Всего позиций: ${products.size}")
        Spacer(modifier = Modifier.height(8.dp))
        LazyColumn {
            items(products) { product ->
                ProductRow(
                    name = product.name,
                    count = product.count,
                    onDelete = { },
                )
            }
        }
    }
}

Spacer — пустое место нужного размера, чтобы отделить список от заголовков. Количество позиций берётся из products.size: если товаров станет больше, строка обновится сама.

Мини-тест: проверьте себя

Закрепите урок: несколько вопросов, разбор ответов — в конце.

  1. Что означает пометка @Composable над функцией?

  2. Нужно поставить название товара и кнопку «Удалить» в одну строку. Какой контейнер выбрать?

  3. Почему список из тысячи товаров показывают через LazyColumn, а не через Column?

  4. Как задать элементу внутренний отступ 16dp?