Модификаторы позволяют декорировать или дополнять составные объекты. С помощью модификаторов можно делать следующее:
- Изменить размер, макет, поведение и внешний вид компонуемого объекта
- Добавьте информацию, например метки доступности.
- Обработка пользовательского ввода
- Добавьте высокоуровневые взаимодействия, например, сделайте элемент кликабельным, прокручиваемым, перетаскиваемым или масштабируемым.
Модификаторы — это стандартные объекты 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
задает для компонуемой заливки максимальную ширину, предоставленную ей родителем.
Рекомендуется, чтобы все ваши компонуемые элементы принимали параметр- modifier
и передавали его первому дочернему элементу, генерирующему пользовательский интерфейс. Это делает код более пригодным для повторного использования, а его поведение — более предсказуемым и интуитивно понятным. Подробнее см. в руководстве по API Compose «Элементы принимают и учитывают параметр-модификатор» .
Порядок модификаторов имеет значение
Порядок функций-модификаторов имеет значение . Поскольку каждая функция вносит изменения в 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 предоставляет список встроенных модификаторов, которые помогут вам украсить или дополнить компонуемый объект. Вот некоторые распространённые модификаторы, которые вы можете использовать для корректировки макетов.
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 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 — Откладывайте чтение как можно дольше ».
Безопасность области действия в 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
Как было показано в предыдущем разделе, посвящённом отступам и размерам , по умолчанию размер компонуемого элемента определяется содержимым, которое он оборачивает. Вы можете сделать компонуемый элемент гибким в пределах его родительского элемента, используя модификатор 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 ) }
Вместо этого вы можете создать, извлечь и повторно использовать один и тот же экземпляр модификатора и передать его в составной объект следующим образом:
// 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 ) }
Извлечение и повторное использование модификаторов без области действия
Модификаторы могут быть как без области действия, так и с областью действия, ограниченной конкретным компонуемым объектом. В случае модификаторов без области действия их можно легко извлечь за пределы любого компонуемого объекта как простые переменные:
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 codelab или обратиться к репозиторию Now in Android .
Дополнительную информацию о пользовательских модификаторах и о том, как их создавать, можно найти в документации Пользовательские макеты — Использование модификатора макета .
{% дословно %}Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Основы создания макета
- Действия редактора {:#editor-actions}
- Пользовательские макеты {:#custom-layouts }