Модификаторы позволяют украшать или дополнять компонуемые функции. Модификаторы позволяют:
- изменять размер, макет, поведение и внешний вид элемента;
- Добавлять информацию, например ярлыки доступности.
- Обработка данных, введенных пользователем
- Добавлять высокоуровневые взаимодействия, например делать элемент кликабельным, прокручиваемым, перетаскиваемым или масштабируемым.
Модификаторы – это стандартные объекты Kotlin. Создайте модификатор, вызвав одну из функций класса Modifier:
@Composable private fun Greeting(name: String) { Column(modifier = Modifier.padding(24.dp)) { Text(text = "Hello,") Text(text = name) } }
Вы можете объединять эти функции в цепочки:
@Composable private fun Greeting(name: String) { Column( modifier = Modifier .padding(24.dp) .fillMaxWidth() ) { Text(text = "Hello,") Text(text = name) } }
В приведенном выше коде вы можете увидеть, как используются разные функции-модификаторы.
paddingдобавляет вокруг элемента пустое пространство.fillMaxWidthпозволяет composable-функции заполнить всю ширину, заданную родительским элементом.
Рекомендуется, чтобы все ваши функции, создающие интерфейс, принимали параметр modifier
и передавали этот модификатор первому дочернему элементу, который создает интерфейс.
Это сделает код более универсальным, а его поведение – более предсказуемым и интуитивно понятным. Подробнее о том, как элементы принимают и учитывают параметр Modifier…
Порядок модификаторов имеет значение
Порядок функций-модификаторов важен. Поскольку каждая функция вносит изменения в объект Modifier, возвращенный предыдущей функцией, последовательность функций влияет на конечный результат. Вот пример:
@Composable fun ArtistCard(/*...*/) { val padding = 16.dp Column( Modifier .clickable(onClick = onClick) .padding(padding) .fillMaxWidth() ) { // rest of the implementation } }
В приведенном выше коде кликабельной является вся область, включая окружающие отступы, поскольку модификатор padding применен после модификатора clickable. Если порядок модификаторов будет обратным, пробел, добавленный padding, не будет реагировать на ввод пользователя:
@Composable fun ArtistCard(/*...*/) { val padding = 16.dp Column( Modifier .padding(padding) .clickable(onClick = onClick) .fillMaxWidth() ) { // rest of the implementation } }
Встроенные модификаторы
Jetpack Compose предоставляет список встроенных модификаторов, которые позволяют украшать или дополнять объекты, созданные с помощью Compose. Ниже приведены некоторые распространенные модификаторы, которые можно использовать для изменения макетов.
padding и size
По умолчанию макеты, предоставленные в Compose, оборачивают свои дочерние элементы. Однако вы можете задать размер, используя модификатор size:
@Composable fun ArtistCard(/*...*/) { Row( modifier = Modifier.size(width = 400.dp, height = 100.dp) ) { Image(/*...*/) Column { /*...*/ } } }
Обратите внимание, что указанный вами размер может не соблюдаться, если он не соответствует ограничениям, заданным родительским элементом макета. Если вам нужно, чтобы размер композиции был фиксированным независимо от входящих ограничений, используйте модификатор requiredSize:
@Composable fun ArtistCard(/*...*/) { Row( modifier = Modifier.size(width = 400.dp, height = 100.dp) ) { Image( /*...*/ modifier = Modifier.requiredSize(150.dp) ) Column { /*...*/ } } }
В этом примере, даже если для родительского элемента height задано значение 100.dp, высота элемента Image будет равна 150.dp, поскольку модификатор requiredSize имеет более высокий приоритет.
Если вы хотите, чтобы дочерний макет занимал всю доступную высоту, заданную родительским макетом, добавьте модификатор fillMaxHeight (в Compose также доступны модификаторы fillMaxSize и fillMaxWidth):
@Composable fun ArtistCard(/*...*/) { Row( modifier = Modifier.size(width = 400.dp, height = 100.dp) ) { Image( /*...*/ modifier = Modifier.fillMaxHeight() ) Column { /*...*/ } } }
Чтобы добавить отступы со всех сторон элемента, задайте модификатор padding.
Если вы хотите добавить отступ над базовой линией текста, чтобы расстояние от верхнего края макета до базовой линии было определенным, используйте модификатор paddingFromBaseline:
@Composable fun ArtistCard(artist: Artist) { Row(/*...*/) { Column { Text( text = artist.name, modifier = Modifier.paddingFromBaseline(top = 50.dp) ) Text(artist.lastSeenOnline) } } }
Смещение
Чтобы разместить макет относительно его исходной позиции, добавьте модификатор offset и задайте смещение по осям x и y.
Смещение может быть как положительным, так и неположительным. Разница между padding и offset заключается в том, что добавление offset к composable-функции не меняет ее размеры:
@Composable fun ArtistCard(artist: Artist) { Row(/*...*/) { Column { Text(artist.name) Text( text = artist.lastSeenOnline, modifier = Modifier.offset(x = 4.dp) ) } } }
Модификатор offset применяется по горизонтали в соответствии с направлением макета.
В контексте слева направо положительное значение offset смещает элемент вправо, а в контексте справа налево – влево.
Если вам нужно задать смещение без учета направления макета, используйте модификатор absoluteOffset, в котором положительное значение смещения всегда сдвигает элемент вправо.
Модификатор offset имеет две перегрузки: offset, которая принимает смещения в качестве параметров, и offset, которая принимает лямбда-выражение.
Подробную информацию о том, когда использовать каждый из этих методов и как оптимизировать их для повышения производительности, можно найти в разделе Compose performance - Defer reads as long as possible.
Область действия безопасности в Compose
В Compose есть модификаторы, которые можно использовать, только если они применяются к дочерним элементам определенных композиций. Compose обеспечивает это с помощью специальных областей.
Например, если вы хотите, чтобы дочерний элемент Box был таким же большим, как родительский, но при этом не хотите менять размер Box, используйте модификатор matchParentSize. matchParentSize доступен только в следующих странах: BoxScope.
Поэтому его можно использовать только в профиле ребенка, связанном с профилем родителя из Box.
Проверка области действия не позволяет добавлять модификаторы, которые не будут работать в других компонентах и областях действия, и экономит время, которое вы могли бы потратить на тестирование.
Модификаторы с областью действия сообщают родителям информацию, которую им следует знать о ребенке. Их также называют модификаторами родительских данных. Внутренние механизмы этих модификаторов отличаются от универсальных, но с точки зрения использования это не имеет значения.
matchParentSize (Box)
Как уже было сказано выше, если вы хотите, чтобы дочерний макет был того же размера, что и родительский
Box, но не хотите менять размер Box, используйте модификатор matchParentSize.
Обратите внимание, что matchParentSize доступен только в области действия Box, то есть применяется только к прямым дочерним элементам композиций Box.
В примере ниже дочерний элемент Spacer наследует размер от родительского элемента Box, который, в свою очередь, наследует размер от самого большого дочернего элемента, в данном случае ArtistCard.
@Composable fun MatchParentSizeComposable() { Box { Spacer( Modifier .matchParentSize() .background(Color.LightGray) ) ArtistCard() } }
Если бы вместо matchParentSize использовался тег fillMaxSize, то элемент Spacer занял бы все доступное пространство, отведенное для родительского элемента, что привело бы к расширению родительского элемента и заполнению им всего доступного пространства.
weight в сервисах "Row" и Column
Как вы уже узнали из предыдущего раздела Отступы и размеры, по умолчанию размер composable-функции определяется контентом, который она содержит. Вы можете задать гибкий размер composable-функции в родительском элементе, используя weight модификатор, который доступен только в RowScope и ColumnScope.
Предположим, у нас есть Row, содержащий два компонуемых элемента Box.
Первый блок имеет в два раза большее значение weight, чем второй, поэтому его ширина в два раза больше. Поскольку ширина Row составляет 210.dp, ширина первого Box равна 140.dp, а ширина второго – 70.dp:
@Composable fun ArtistCard(/*...*/) { Row( modifier = Modifier.fillMaxWidth() ) { Image( /*...*/ modifier = Modifier.weight(2f) ) Column( modifier = Modifier.weight(1f) ) { /*...*/ } } }
Извлечение и повторное использование модификаторов
Несколько модификаторов можно объединить в цепочку, чтобы украсить или дополнить компонуемый элемент. Эта цепочка создается через интерфейс Modifier, который представляет собой упорядоченный неизменяемый список отдельных Modifier.Elements.
Каждый символ Modifier.Element представляет собой отдельное поведение, например макет, рисование и графику, все действия, связанные с жестами, фокусом и семантикой, а также события ввода с устройства. Порядок имеет значение: элементы-модификаторы, добавленные первыми, будут применены первыми.
Иногда бывает полезно повторно использовать одни и те же экземпляры цепочки модификаторов в нескольких композициях, извлекая их в переменные и поднимая в более высокие области видимости. Это может повысить читабельность кода или помочь улучшить производительность приложения по нескольким причинам:
- Перераспределение модификаторов не будет повторяться при повторной композиции для композируемых функций, которые их используют.
- Цепочки модификаторов могут быть очень длинными и сложными, поэтому повторное использование одного и того же экземпляра цепочки может облегчить рабочую нагрузку, которую среда выполнения Compose должна выполнять при их сравнении.
- Это позволяет сделать код более чистым, единообразным и удобным для поддержки.
Рекомендации по повторному использованию модификаторов
Создавайте собственные цепочки Modifier и извлекайте их, чтобы использовать в нескольких составных компонентах. Сохранять модификаторы можно так же, как и объекты данных:
val reusableModifier = Modifier .fillMaxWidth() .background(Color.Red) .padding(12.dp)
Извлечение и повторное использование модификаторов при наблюдении за часто меняющимся состоянием
При наблюдении за часто меняющимися состояниями внутри композиций, например состояниями анимации или scrollState, может выполняться значительное количество повторных композиций. В этом случае модификаторы будут выделяться при каждом изменении композиции и, возможно, для каждого кадра:
@Composable fun LoadingWheelAnimation() { val animatedState = animateFloatAsState(/*...*/) LoadingWheel( // Creation and allocation of this modifier will happen on every frame of the animation! modifier = Modifier .padding(12.dp) .background(Color.Gray), animatedState = animatedState ) }
Вместо этого вы можете создать, извлечь и повторно использовать один и тот же экземпляр модификатора и передать его в composable-функцию следующим образом:
// Now, the allocation of the modifier happens here: val reusableModifier = Modifier .padding(12.dp) .background(Color.Gray) @Composable fun LoadingWheelAnimation() { val animatedState = animateFloatAsState(/*...*/) LoadingWheel( // No allocation, as we're just reusing the same instance modifier = reusableModifier, animatedState = animatedState ) }
Извлечение и повторное использование модификаторов без области действия
Модификаторы могут быть без области действия или с областью действия, ограниченной определенной composable-функцией. В случае с модификаторами без области действия их можно легко извлечь за пределы композиций в виде простых переменных:
val reusableModifier = Modifier .fillMaxWidth() .background(Color.Red) .padding(12.dp) @Composable fun AuthorField() { HeaderText( // ... modifier = reusableModifier ) SubtitleText( // ... modifier = reusableModifier ) }
Это может быть особенно полезно в сочетании с ленивой загрузкой. В большинстве случаев вам нужно, чтобы все товары (а их может быть много) имели одинаковые модификаторы:
val reusableItemModifier = Modifier .padding(bottom = 12.dp) .size(216.dp) .clip(CircleShape) @Composable private fun AuthorList(authors: List<Author>) { LazyColumn { items(authors) { AsyncImage( // ... modifier = reusableItemModifier, ) } } }
Извлечение и повторное использование модификаторов с областью действия
Если модификаторы относятся к определенным функциям, их можно извлечь на самый высокий возможный уровень и использовать повторно, где это необходимо:
Column(/*...*/) { val reusableItemModifier = Modifier .padding(bottom = 12.dp) // Align Modifier.Element requires a ColumnScope .align(Alignment.CenterHorizontally) .weight(1f) Text1( modifier = reusableItemModifier, // ... ) Text2( modifier = reusableItemModifier // ... ) // ... }
Извлеченные модификаторы с определенной областью действия следует передавать только дочерним элементам с той же областью действия. Подробнее о том, почему это важно, можно узнать в разделе Область видимости в Compose.
Column(modifier = Modifier.fillMaxWidth()) { // Weight modifier is scoped to the Column composable val reusableItemModifier = Modifier.weight(1f) // Weight will be properly assigned here since this Text is a direct child of Column Text1( modifier = reusableItemModifier // ... ) Box { Text2( // Weight won't do anything here since the Text composable is not a direct child of Column modifier = reusableItemModifier // ... ) } }
Дальнейшее объединение извлеченных модификаторов
Вы можете объединить или добавить цепочки модификаторов, вызвав функцию .then():
val reusableModifier = Modifier .fillMaxWidth() .background(Color.Red) .padding(12.dp) // Append to your reusableModifier reusableModifier.clickable { /*...*/ } // Append your reusableModifier otherModifier.then(reusableModifier)
Помните, что порядок модификаторов имеет значение.
Подробнее…
Мы подготовили полный список модификаторов с их параметрами и областями действия.
Чтобы лучше понять, как использовать модификаторы, вы также можете выполнить практическую работу по созданию базовых макетов в Compose или изучить репозиторий Now in Android.
Дополнительную информацию о специальных модификаторах и о том, как их создавать, можно найти в документации по специальным макетам и использованию модификатора макета.
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Основные сведения о макетах в Compose
- Действия редактора {:#editor-actions}
- Собственные макеты {:#custom-layouts }