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, де властивості задавалися через setter (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, потім відступ 10dp зовні (підсумковий розмір 120dp). padding(10.dp).size(100.dp) — відступ 10dp зменшує доступний простір до (батько - 20dp), потім size(100dp) може переповнити батьківський елемент. Завжди продумуйте порядок свідомо, використовуючи тести відображення для перевірки результату.
| Порядок | Результат |
|---|---|
| padding → clickable | Клік працює і на області відступу |
| clickable → padding | Клік працює тільки на контенті, відступ — мертва зона |
| size → padding | Елемент size(100), відступ зовні → 100+2*pad |
| padding → size | Відступ зменшує місце, 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 {} визначає, як елемент буде представлений в дереві доступності. Compose автоматично заповнює семантику з тексту, але для кастомних компонентів потрібно задавати ролі, стани та дії вручну. Це критично важливо для відповідності WCAG 2.2 та коректної роботи TalkBack (Android) та VoiceOver (iOS).
@Composable
fun ModifierDemo() {
// Ланцюжок модифікаторів з правильним порядком
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("Торкни мене")
}
}
Modifier.composed — це фабричний метод, що дозволяє створювати композитні модифікатори, які можуть використовувати інші модифікатори, LocalComposition та локальний стан. На відміну від звичайної функції-розширення, composed створює екземпляр кожного разу при застосуванні, що дозволяє мати власний стан всередині модифікатора.
Коли використовувати composed: повторювані комбінації модифікаторів (наприклад, стандартний стиль картки: padding + background + border + clickable); модифікатори зі станом (анімована зміна фону при натисканні); доступ до CompositionLocals (кольорова схема MaterialTheme, щільність пікселів). У звичайних випадках достатньо звичайної функції-розширення без composed.
Продуктивність composed: кожен виклик створює новий об’єкт модифікатора, що може призвести до зайвих алокацій при рекомпозиції. Для запобігання цьому обгортайте composed в remember. Google рекомендує використовувати composed тільки тоді, коли всередині дійсно потрібен стан або CompositionLocal. Для статичних комбінацій використовуйте звичайні функції-розширення.
// Кастомний модифікатор через composed зі станом
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))
}
// Приклад використання
@Composable
fun CardList() {
Column {
Box(Modifier.cardStyle()) { Text("Елемент 1") }
Box(Modifier.cardStyle(isSelected = true)) { Text("Вибрано") }
}
}
// Статична версія (без composed) — швидше
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 вкладених.
// ✅ Добре: константа поза Composable
private val cardModifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
@Composable
fun CardContent() {
Box(cardModifier.background(Color.White)) { ... }
}
// ❌ Погано: перестворення при кожній рекомпозиції
@Composable
fun BadCard() {
Box(Modifier.fillMaxWidth().padding(16.dp)) { ... }
}
// ✅ Добре: remember для динамічного 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() автоматично додає роль Кнопки та Action(OnClick). Для кастомних жестів потрібно явно вказувати semantics. Без семантичних модифікаторів користувачі TalkBack не зможуть взаємодіяти з кастомними компонентами.
Modifier.background(color, shape) працює з кутами, але clip() повинен йти ДО background, щоб кути обрізалися. Правильний порядок: clip(shape).background(color). Якщо потрібно обрізати і контент всередині, використовуйте clipToBounds() на батьківському елементі.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також