Jetpack Compose значительно упрощает проектирование и создание пользовательского интерфейса приложения. Compose преобразует состояние в элементы пользовательского интерфейса с помощью:
- Композиция элементов
- Макет элементов
- Рисование элементов
В этом документе основное внимание уделяется макету элементов и объясняются некоторые строительные блоки, которые Compose предоставляет для помощи в компоновке элементов пользовательского интерфейса.
Цели макетов в Compose
Реализация системы макетов в Jetpack Compose преследует две основные цели:
- Высокая производительность
- Возможность легко создавать собственные макеты.
Основы composable-функций
Компонуемые функции – это базовый строительный блок Compose. Composable-функция – это функция, которая генерирует Unit, описывающий часть пользовательского интерфейса. Функция принимает некоторые входные данные и генерирует то, что показано на экране. Дополнительную информацию о composable-функциях можно найти в документации Ментальная модель Compose.
Composable-функция может вызывать несколько элементов интерфейса. Однако, если вы не предоставить рекомендации о том, как они должны быть организованы, 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") } }
Часто этих блоков достаточно. Вы можете написать собственную composable-функцию, чтобы объединить эти макеты в более сложный макет, подходящий для вашего приложения.
Чтобы задать положение дочерних элементов в 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.
Поскольку измерение и размещение являются отдельными подфазами прохода макета, любые изменения, которые влияют только на размещение элементов, а не на измерение, могут выполняться отдельно.
Как использовать модификаторы в макетах
Как описано в разделе Модификаторы 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позволяет composable-функции реагировать на действия пользователя и показывает пульсацию.paddingдобавляет пространство вокруг элемента.fillMaxWidthзаставляет composable-функцию заполнять максимальную ширину, заданную родительским элементом.size()указывает предпочтительную ширину и высоту элемента.
Прокручиваемые макеты
Дополнительную информацию о макетах с прокруткой можно найти в документации по жестам в Compose.
Дополнительную информацию о списках и ленивых списках можно найти в документации по спискам Compose.
Адаптивные макеты
При разработке макета следует учитывать различные ориентации экрана и размеры форм-факторов. Compose предлагает несколько готовых механизмов, которые упрощают адаптацию макетов к различным конфигурациям экрана.
Ограничения
Чтобы узнать ограничения, поступающие от родительского элемента, и соответствующим образом спроектировать макет, вы можете использовать BoxWithConstraints. Ограничения по размеру можно найти в области лямбда-выражения контента. Вы можете использовать эти ограничения, чтобы создавать разные макеты для разных конфигураций экрана:
@Composable fun WithConstraintsComposable() { BoxWithConstraints { Text("My minHeight is $minHeight while my maxWidth is $maxWidth") } }
Макеты на основе рекламных мест
Compose предоставляет большое количество composable-функций на основе Material Design с зависимостью androidx.compose.material:material (включается при создании проекта Compose в Android Studio), что упрощает создание пользовательского интерфейса. Предоставляются такие элементы, как Drawer, FloatingActionButton и TopAppBar.
Компоненты Material активно используют API слотов – шаблон, который Compose вводит для добавления уровня настройки поверх composable-функций. Этот подход делает компоненты более гибкими, поскольку они принимают дочерний элемент, который может настраиваться сам, вместо того, чтобы раскрывать каждый параметр конфигурации дочернего элемента.
Слоты оставляют в интерфейсе пустое место, которое разработчик может заполнить по своему усмотрению. Например, в TopAppBar можно настроить следующие слоты:
Обычно composable-функции принимают лямбда-выражение 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 -> // ... } } }
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Модификаторы Compose
- Kotlin для Jetpack Compose
- Компоненты и макеты Material Design