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

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

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

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

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

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

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

Основы составных функций

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

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

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

Компоненты материалов активно используют 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 ->
            // ...
        }
    }
}

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