Modifier — это неизменяемый объект в Jetpack Compose, который определяет свойства UI-компонента: размер, отступы, фон, обработку жестов и поведение. Модификаторы объединяются в цепочку через последовательный вызов, причём порядок их применения критически влияет на результат. По данным Google Android Developers, 2026, правильное использование Modifier является основой построения гибкого и производительного интерфейса в декларативном UI.
Главное
Modifier — это интерфейс из пакета androidx.compose.ui, реализующий паттерн Composite. Каждый модификатор — это элемент цепочки, который оборачивает предыдущий и добавляет своё поведение. Modifier является неизменяемым — любые изменения создают новый объект через копирование с добавлением нового элемента в цепочку. Это позволяет безопасно разделять один Modifier между множеством компонентов.
Базовые функции-модификаторы вызываются через объект-компаньон Modifier (например, Modifier.padding(), Modifier.fillMaxWidth()). Каждая функция возвращает новый Modifier с добавленным элементом. Если модификаторов несколько, они объединяются в цепочку: Modifier.padding(16.dp).fillMaxWidth().background(Color.Blue). Порядок — это направление от внешнего к внутреннему относительно UI-элемента.
В отличие от традиционных View, где свойства задавались через setters (view.setPadding(...), view.setBackground(...)), в Compose Modifier — это декларативное описание. Компонент не "применяет" модификаторы во время работы — LayoutNode на этапе композиции проходит по цепочке Modifier и собирает из неё список Modifier.Element, которые затем обрабатываются на этапе измерения и расположения.
Порядок модификаторов — одна из самых частых ошибок в Compose. Каждый модификатор оборачивает предыдущий, и операции выполняются снаружи внутрь. Например, padding(16.dp).clickable { }: сначала добавляется отступ вокруг элемента, затем область клика включает и отступ. clickable { }.padding(16.dp): сначала область клика равна размеру элемента, затем отступ добавляется вокруг — клик по отступу не сработает.
Правило запоминания: читайте цепочку слева направо и применяйте снаружи внутрь. Первый модификатор — самый внешний, он применяется к области вокруг элемента. Последний — самый внутренний, применяется непосредственно к контенту. Модификаторы размера (size, fillMaxWidth) должны идти после отступов, если отступ нужен от родителя, или до отступов, если контент должен быть сначала ограничен, а потом отцентрирован.
Пример: size(100.dp).padding(10.dp) — элемент фиксированного размера 100dp, затем padding 10dp снаружи (итоговый размер 120dp). padding(10.dp).size(100.dp) — padding 10dp уменьшает доступное пространство до (родитель - 20dp), затем size(100dp) может переполнить родителя. Всегда продумывайте порядок осознанно, используя тесты отображения для проверки результата.
| Порядок | Результат |
|---|---|
| padding → clickable | Клик работает и на области отступа |
| clickable → padding | Клик работает только на контенте, отступ — мёртвая зона |
| size → padding | Элемент size(100), padding снаружи → 100+2*pad |
| padding → size | padding уменьшает место, size может выйти за границы |
| background → padding | Фон заливает весь элемент включая внешнюю область |
| padding → background | Фон только внутри отступа (внешняя область прозрачна) |
Стандартная библиотека Compose включает ~50+ модификаторов, разделённых на категории. Размер и позиционирование: Modifier.size(), width(), height(), fillMaxSize(), fillMaxWidth(), fillMaxHeight(), defaultMinSize(), requiredSize(). Отступы и границы: padding(), offset(), margin (размещается через padding родителя или Layout). Декор: background(), border(), clip(), alpha(), shadow(), blur().
Поведение и жесты: clickable(), combinedClickable(), pointerInput(), draggable(), swipeable(). Расположение в контейнере: weight() (для Row/Column), align(), alignBy(), matchParentSize(). Семантика и доступность: semantics(), testTag(), clearAndSetSemantics(). Рисование: drawBehind(), drawWithContent(), drawModifier() — модификаторы, позволяющие кастомное рисование на канвасе.
Семантические модификаторы — особая категория. Modifier.semantics {} определяет, как элемент будет представлен в Accessibility-дереве. Compose автоматически заполняет семантику из текста, но для кастомных компонентов нужно задавать роли, состояния и действия вручную. Это критически важно для соответствия WCAG 2.2 и корректной работы TalkBack (Android) и VoiceOver (iOS).
@Composable
fun ModifierDemo() {
// Modifier chain with correct order
Box(
modifier = Modifier
.size(150.dp)
.padding(8.dp)
.border(2.dp, Color.Gray)
.background(Color(0xFFE3F2FD))
.clickable { /* handle click */ }
.semantics {
contentDescription = "Demo card with click action"
role = Role.Button
}
) {
Text("Touch me")
}
}
Modifier.composed — это фабричный метод, позволяющий создавать композитные модификаторы, которые могут использовать другие модификаторы, LocalComposition и локальное состояние. В отличие от обычной функции-расширения, composed создаёт экземпляр каждый раз при применении, что позволяет иметь собственное состояние внутри модификатора.
Когда использовать composed: повторяющиеся комбинации модификаторов (например, стандартный стиль карточки: padding + background + border + clickable); модификаторы с состоянием (анимированная смена background при нажатии); доступ к CompositionLocals (цветовая схема MaterialTheme, плотность пикселей). В обычных случаях достаточно обычной функции-расширения без composed.
Производительность composed: каждый вызов создаёт новый объект модификатора, что может привести к лишним аллокациям при рекомпозиции. Для предотвращения оборачивайте composed в remember. Google рекомендует использовать composed только когда внутри действительно нужно состояние или CompositionLocal. Для статических комбинаций используйте обычные extension-функции.
// Custom modifier via composed with state
fun Modifier.cardStyle(
elevation: Dp = 4.dp,
isSelected: Boolean = false
): Modifier = this.composed {
val backgroundColor = if (isSelected)
MaterialTheme.colorScheme.primaryContainer
else
MaterialTheme.colorScheme.surface
this
.fillMaxWidth()
.padding(12.dp)
.background(backgroundColor, RoundedCornerShape(8.dp))
.shadow(elevation, RoundedCornerShape(8.dp))
}
// Usage example
@Composable
fun CardList() {
Column {
Box(Modifier.cardStyle()) { Text("Item 1") }
Box(Modifier.cardStyle(isSelected = true)) { Text("Selected") }
}
}
// Static version (without composed) — faster
fun Modifier.simpleCardStyle(): Modifier =
this.fillMaxWidth().padding(8.dp).clip(RoundedCornerShape(4.dp))
Избегайте пересоздания Modifier при каждой рекомпозиции. Если модификатор не зависит от изменяемых данных — вынесите его в константу или remember. Каждый раз при вызове Modifier.padding().background() создаются новые объекты Modifier.Element. В изолированном компоненте это незаметно, но в LazyColumn с сотнями элементов лишние аллокации вызывают заметные лаги при скролле.
Правило: если цепочка модификаторов не зависит от параметров Composable-функции — объявите её как val вне функции (на уровне файла или Companion). Если зависит — используйте remember(зависимость) { ... }. Для модификаторов, которые всегда одинаковы, эффективнее всего val за пределами Composable: такие объекты создаются один раз за всё время жизни приложения.
Modifier ordering best practices: располагайте модификаторы в логическом порядке: сначала размер/отступы (layout), потом декор (background, border), затем поведение (clickable, pointerInput). Это не только улучшает читаемость, но и помогает Compose Runtime оптимизировать цепочку на этапе измерения. Также избегайте избыточных вложенных Box с разными Modifier — часто один Modifier на родительском контейнере может заменить 2-3 вложенных.
// ✅ Good: constant outside Composable
private val cardModifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
@Composable
fun CardContent() {
Box(cardModifier.background(Color.White)) { ... }
}
// ❌ Bad: recreation on every recomposition
@Composable
fun BadCard() {
Box(Modifier.fillMaxWidth().padding(16.dp)) { ... }
}
// ✅ Good: remember for dynamic Modifier
@Composable
fun DynamicCard(color: Color) {
val modifier = remember(color) {
Modifier.fillMaxWidth().background(color)
}
Box(modifier) { ... }
}
Часто задаваемые вопросы
Да, Modifier неизменяемый, поэтому один объект можно безопасно использовать в нескольких местах. Однако если вы используете composed-модификатор, каждый вызов создаёт новый экземпляр. Для статических цепочек константа или val вне Composable — оптимальное решение.
Используйте Layout Inspector в Android Studio — он визуально показывает границы каждого Modifier. Для программной отладки добавьте Modifier.border() с разными цветами на каждом шаге цепочки, чтобы увидеть границы применения каждого модификатора.
Modifier.then(other) присоединяет цепочку other к this. Последовательный вызов (Modifier.a().b()) эквивалентен Modifier.then(a()).then(b()). Разницы нет — это один и тот же механизм цепочки. then() полезен, когда нужно присоединить готовую цепочку из переменной.
Modifier.semantics {} определяет, как элемент будет описан для screen reader. Modifier.clickable() автоматически добавляет роль Button и Action(OnClick). Для кастомных жестов нужно явно указывать semantics. Без семантических модификаторов пользователи TalkBack не смогут взаимодействовать с кастомными компонентами.
Modifier.background(color, shape) работает с углами, но clip() должен идти ДО background, чтобы углы обрезались. Правильный порядок: clip(shape).background(color). Если нужно обрезать и контент внутри, используйте clipToBounds() на родителе.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также