Modifier — ланцюжок модифікаторів та продуктивність в Compose

Автор: IT Sectr Опубліковано: 2026-06-28 Час читання: 8 хв

Modifier — це незмінний об’єкт в Jetpack Compose, який визначає властивості UI-компонента: розмір, відступи, фон, обробку жестів та поведінку. Модифікатори об’єднуються в ланцюжок через послідовний виклик, причому порядок їх застосування критично впливає на результат. За даними Google Android Developers, 2026, правильне використання Modifier є основою побудови гнучкого та продуктивного інтерфейсу в декларативному UI.

Головне

  • Modifier — незмінний об’єкт, що описує зовнішній вигляд та поведінку UI-компонента
  • Ланцюжок модифікаторів будується послідовно, порядок впливає на відображення
  • Порядок має значення: padding → size відрізняється від size → padding
  • Modifier.composed дозволяє створювати власні композитні модифікатори
  • Оптимізація: уникайте перестворення Modifier при кожній рекомпозиції

Що таке Modifier в Jetpack Compose

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).

kotlin
@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

Modifier.composed — це фабричний метод, що дозволяє створювати композитні модифікатори, які можуть використовувати інші модифікатори, LocalComposition та локальний стан. На відміну від звичайної функції-розширення, composed створює екземпляр кожного разу при застосуванні, що дозволяє мати власний стан всередині модифікатора.

Коли використовувати composed: повторювані комбінації модифікаторів (наприклад, стандартний стиль картки: padding + background + border + clickable); модифікатори зі станом (анімована зміна фону при натисканні); доступ до CompositionLocals (кольорова схема MaterialTheme, щільність пікселів). У звичайних випадках достатньо звичайної функції-розширення без composed.

Продуктивність composed: кожен виклик створює новий об’єкт модифікатора, що може призвести до зайвих алокацій при рекомпозиції. Для запобігання цьому обгортайте composed в remember. Google рекомендує використовувати composed тільки тоді, коли всередині дійсно потрібен стан або CompositionLocal. Для статичних комбінацій використовуйте звичайні функції-розширення.

kotlin
// Кастомний модифікатор через 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 та кращі практики

Уникайте перестворення 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 вкладених.

kotlin
// ✅ Добре: константа поза 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 для декількох Composable?

Так, Modifier незмінний, тому один об’єкт можна безпечно використовувати в кількох місцях. Однак якщо ви використовуєте composed-модифікатор, кожен виклик створює новий екземпляр. Для статичних ланцюжків константа або val поза Composable — оптимальне рішення.

Як налагоджувати ланцюжок модифікаторів?

Використовуйте Layout Inspector в Android Studio — він візуально показує межі кожного Modifier. Для програмного налагодження додайте Modifier.border() з різними кольорами на кожному кроці ланцюжка, щоб побачити межі кожного модифікатора.

Що таке Modifier.then() і чим він відрізняється від послідовного виклику?

Modifier.then(other) прієднує ланцюжок other до this. Послідовний виклик (Modifier.a().b()) еквівалентний Modifier.then(a()).then(b()). Різниці немає — це один і той самий механізм ланцюжка. then() корисний, коли потрібно прієднати готовий ланцюжок зі змінної.

Як Modifier впливає на семантику Accessibility?

Modifier.semantics {} визначає, як елемент буде описаний для screen reader. Modifier.clickable() автоматично додає роль Кнопки та Action(OnClick). Для кастомних жестів потрібно явно вказувати semantics. Без семантичних модифікаторів користувачі TalkBack не зможуть взаємодіяти з кастомними компонентами.

Чому background в Modifier не працює з закругленими кутами?

Modifier.background(color, shape) працює з кутами, але clip() повинен йти ДО background, щоб кути обрізалися. Правильний порядок: clip(shape).background(color). Якщо потрібно обрізати і контент всередині, використовуйте clipToBounds() на батьківському елементі.

Підсумки

  • Modifier — незмінний об’єкт для декларативного опису зовнішнього вигляду та поведінки
  • Порядок модифікаторів визначає результат: padding → clickable vs clickable → padding
  • Ланцюжок будується послідовно, кожен елемент обгортає попередній
  • Modifier.composed дозволяє створювати модифікатори зі станом та CompositionLocal
  • Продуктивність: виносіть статичні ланцюжки в константи, використовуйте remember для динамічних
  • Семантика: Modifier.semantics обов’язковий для Accessibility кастомних компонентів
  • Рекомендація: розташовуйте модифікатори від layout до декору, потім до поведінки

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також