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

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 .

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

Адаптивная верстка

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

Ограничения

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

@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 :

Диаграмма, показывающая доступные слоты на панели приложения Material Components.

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

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

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

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

{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}