Jetpack Compose значительно упрощает проектирование и создание пользовательского интерфейса вашего приложения. Compose преобразует состояние в элементы пользовательского интерфейса с помощью:
- Состав элементов
- Расположение элементов
- Рисование элементов
Этот документ посвящен компоновке элементов и объясняет некоторые строительные блоки, которые 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
структура дерева пользовательского интерфейса соответствует следующему порядку:
- Корневой узел
Row
предлагается измерить. - Корневой узел
Row
запрашивает измерение у своего первого дочернего элементаImage
. -
Image
— это листовой узел (то есть у него нет дочерних элементов), поэтому он сообщает размер и возвращает инструкции по размещению. - Корневой узел
Row
запрашивает измерение у своего второго дочернего узлаColumn
. - Узел
Column
запрашивает измерение у своего первого дочернего элементаText
. - Первый
Text
узел является листовым узлом, поэтому он сообщает размер и возвращает инструкции по размещению. - Узел
Column
запрашивает измерение у своего второго дочернего элементаText
. - Второй
Text
узел является листовым узлом, поэтому он сообщает размер и возвращает инструкции по размещению. - Теперь, когда узел
Column
измерил, определил размер и разместил своих дочерних элементов, он может определить свой собственный размер и размещение. - Теперь, когда корневой узел
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
, легко убедиться, что эти компоненты правильно расположены и правильно работают вместе.
@Composable
fun HomeScreen(/*...*/) {
ModalNavigationDrawer(drawerContent = { /* ... */ }) {
Scaffold(
topBar = { /*...*/ }
) { contentPadding ->
// ...
}
}
}