Основы составления макета

Jetpack Compose значительно упрощает проектирование и разработку пользовательского интерфейса вашего приложения. Compose преобразует состояние в элементы пользовательского интерфейса с помощью:

  1. Состав элементов
  2. Расположение элементов
  3. Чертеж элементов

Составьте преобразование состояния в пользовательский интерфейс посредством композиции, макета и рисования

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

Цели макетов в Compose

Реализация системы макетов Jetpack Compose имеет две основные цели:

Основы компонуемых функций

Компонуемые функции — это базовый строительный блок Compose. Компонуемая функция — это функция, создающая Unit , которая описывает часть вашего пользовательского интерфейса. Функция принимает входные данные и генерирует то, что отображается на экране. Подробнее о компонуемых функциях см. в документации по ментальной модели Compose .

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

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Без указания того, как вы хотите их расположить, Compose накладывает текстовые элементы друг на друга, делая их нечитаемыми:

Два текстовых элемента, нарисованных друг на друге, делают текст нечитаемым

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

Стандартные компоненты компоновки

Во многих случаях вы можете просто использовать стандартные элементы макета Compose .

Используйте Column для вертикального размещения элементов на экране.

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Два текстовых элемента, расположенных в столбце, что позволяет легко прочитать текст.

Аналогично, используйте Row для горизонтального размещения элементов на экране. Как Column , так и Row поддерживают настройку выравнивания содержащихся в них элементов.

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

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

Используйте Box для размещения элементов друг над другом. Box также поддерживает настройку выравнивания содержащихся в нём элементов.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

Показывает два элемента, расположенных друг над другом.

Часто этих стандартных блоков достаточно. Вы можете написать собственную функцию компоновки, чтобы объединить эти макеты в более сложный макет, подходящий для вашего приложения.

Сравнивает три простых компоновочных макета: столбец, строка и блок

Чтобы задать положение дочерних элементов в Row , задайте аргументы horizontalArrangement и verticalAlignment . Для Column задайте аргументы verticalArrangement и horizontalAlignment :

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

Элементы выравниваются по правому краю

Модель компоновки

В модели макета дерево пользовательского интерфейса размечается за один проход. Сначала каждому узлу предлагается измерить себя, затем рекурсивно измерить все дочерние элементы, передавая ограничения по размеру вниз по дереву. Затем определяются размеры и размещаются листовые узлы, а определённые размеры и инструкции по размещению передаются обратно вверх по дереву.

Коротко говоря, родители снимают мерки раньше своих детей, но их размеры и размещение определяются после детей.

Рассмотрим следующую функцию SearchResult .

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

Эта функция создает следующее дерево пользовательского интерфейса.

SearchResult
  Row
    Image
    Column
      Text
      Text

В примере SearchResult структура дерева пользовательского интерфейса имеет следующий порядок:

  1. Корневой узел Row просят измерить.
  2. Корневой узел Row запрашивает у своего первого дочернего узла Image выполнение измерений.
  3. Image является конечным узлом (т.е. не имеет дочерних элементов), поэтому оно сообщает размер и возвращает инструкции по размещению.
  4. Корневой узел Row запрашивает у своего второго дочернего узла Column измерение.
  5. Узел Column запрашивает у своего первого дочернего элемента Text измерение.
  6. Первый Text узел является конечным узлом, поэтому он сообщает размер и возвращает инструкции по размещению.
  7. Узел Column запрашивает у своего второго дочернего элемента Text измерение.
  8. Второй узел Text является конечным узлом, поэтому он сообщает размер и возвращает инструкции по размещению.
  9. Теперь, когда узел Column измерил, определил размер и разместил свои дочерние элементы, он может определить свой собственный размер и размещение.
  10. Теперь, когда корневой узел Row измерил, определил размер и разместил своих дочерних элементов, он может определить свой собственный размер и размещение.

Порядок измерения, определения размера и размещения в дереве результатов поиска

Производительность

Compose достигает высокой производительности, измеряя дочерние элементы только один раз. Однопроходное измерение положительно влияет на производительность, позволяя Compose эффективно обрабатывать глубокие древовидные структуры пользовательского интерфейса. Если элемент измеряет свой дочерний элемент дважды, а этот дочерний элемент измеряет каждый из своих дочерних элементов дважды и так далее, то для одной попытки компоновки всего пользовательского интерфейса потребуется выполнить большой объем работы, что затруднит поддержание производительности приложения.

Если по какой-то причине вашему макету требуется несколько измерений, Compose предлагает специальную систему — внутренние измерения . Подробнее об этой функции можно узнать в статье «Внутренние измерения в макетах Compose» .

Поскольку измерение и размещение являются отдельными подэтапами этапа компоновки, любые изменения, которые влияют только на размещение элементов, а не на измерения, могут быть выполнены отдельно.

Использование модификаторов в макетах

Как обсуждалось в разделе «Модификаторы композиции» , вы можете использовать модификаторы для украшения или дополнения своих компонуемых элементов. Модификаторы необходимы для настройки макета. Например, здесь мы объединяем несколько модификаторов для настройки ArtistCard :

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

Еще более сложная компоновка, использующая модификаторы для изменения расположения графики и того, какие области реагируют на действия пользователя.

В приведенном выше коде обратите внимание на совместное использование различных функций-модификаторов.

  • clickable создает реакцию на пользовательский ввод и отображает рябь.
  • padding создает пространство вокруг элемента.
  • fillMaxWidth задает для компонуемой заливки максимальную ширину, предоставленную ей родителем.
  • size() определяет предпочтительную ширину и высоту элемента.

Прокручиваемые макеты

Дополнительную информацию о прокручиваемых макетах можно найти в документации по жестам создания сообщений .

Информацию о списках и ленивых списках см. в документации по составлению списков .

Адаптивные макеты

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

Ограничения

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

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Макеты на основе слотов

Compose предоставляет широкий выбор компонуемых элементов на основе Material Design с зависимостью androidx.compose.material:material (включаемой при создании проекта Compose в Android Studio) для упрощения разработки пользовательского интерфейса. Доступны такие элементы, как Drawer , FloatingActionButton и TopAppBar .

Компоненты Material активно используют API слотов — шаблон, который Compose внедряет для создания уровня настройки поверх компонуемых элементов. Такой подход делает компоненты более гибкими, поскольку они принимают дочерний элемент, который может настраиваться самостоятельно, вместо того, чтобы предоставлять доступ ко всем параметрам конфигурации дочернего элемента. Слоты оставляют пустое пространство в пользовательском интерфейсе, которое разработчик может заполнить по своему усмотрению. Например, вот слоты, которые можно настроить в TopAppBar :

Диаграмма, показывающая доступные слоты на панели приложений «Компоненты материалов»

Компонуемые элементы обычно принимают лямбда-функцию компонуемого content ( content: @Composable () -> Unit ). API слотов предоставляют несколько параметров content для определённых целей. Например, TopAppBar позволяет указать контент для title , navigationIcon и actions .

Например, Scaffold позволяет реализовать пользовательский интерфейс с базовой структурой макета Material Design. Scaffold предоставляет слоты для наиболее распространённых компонентов Material верхнего уровня, таких как TopAppBar , BottomAppBar , FloatingActionButton и Drawer . Используя Scaffold , можно легко обеспечить правильное расположение этих компонентов и их корректную совместную работу.

Пример приложения JetNews, которое использует Scaffold для позиционирования нескольких элементов

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}

{% дословно %} {% endverbatim %} {% дословно %} {% endverbatim %}