Розбираємося, що таке ConstraintLayout — гнучка система позиціонування для Android, яка дозволяє будувати пласкі ієрархії в'ю за допомогою констрейнтів (обмежень) замість вкладених LinearLayout та RelativeLayout. ConstraintLayout вирішує проблему «пекельної вкладеності» (layout nesting hell), скорочуючи глибину ієрархії до одного рівня та прискорюючи відображення екрана. Бібліотека входить до Jetpack і доступна починаючи з Android 2.3 (API 9) через support-library. Основні механіки описані в офіційній документації Android.
Головне
ConstraintLayout — це ViewGroup з бібліотеки AndroidX ConstraintLayout, призначена для створення гнучких та продуктивних інтерфейсів через декларативні обмеження (constraints). На відміну від LinearLayout, який вибудовує елементи в одну лінію, або RelativeLayout, який позиціонує відносно сусідів, ConstraintLayout дозволяє закріпити кожен елемент відносно будь-яких інших елементів та батька одночасно.
Бібліотека була анонсована на Google I/O 2016 як рішення для прискорення рендерингу складних екранів. Ключова проблема, яку вирішує ConstraintLayout — вкладеність макетів. Кожен вкладений ViewGroup додає мінімум два проходи measure і один прохід layout. Екран з 4 рівнями вкладеності виконує 8 проходів measure; ConstraintLayout з тією ж функціональністю — всього 2 проходи. За даними Google (Android Performance Blog, 2017), заміна трьох вкладених LinearLayout на один ConstraintLayout скорочує час onMeasure на 40%.
Поточна версія ConstraintLayout 2.1.4 стабільно працює на Android 2.3+ (API 9) через AndroidX. У версії 2.0 з'явилися кругове позиціонування, Flow (автоматичне перенесення елементів) та підтримка MotionLayout. ConstraintLayout обов'язковий для розуміння сучасної Android-розробки — він використовується в Jetpack Compose як базова концепція модифікаторів, у шаблонах Android Studio за замовчуванням та в Material Design 3.
Пласка ієрархія ConstraintLayout означає, що всі дочірні View знаходяться на одному рівні вкладеності. Замість того щоб поміщати елемент A в LinearLayout, а LinearLayout — в RelativeLayout, всі елементи прив'язуються безпосередньо до батьківського ConstraintLayout або один до одного через атрибути. Це дає: менше споживання пам'яті (кожен ViewGroup — об'єкт у Java heap), прискорення layout pass (менше рекурсивних викликів), більш передбачувану поведінку при зміні розмірів екрана.
Констрейнт (обмеження) — це зв'язок між краєм одного View (або його центром) та краєм іншого View або батька. Кожен View може мати до 8 констрейнтів: left, top, right, bottom, start, end, baseline та center. Мінімально для позиціонування достатньо двох перпендикулярних констрейнтів (наприклад, top + left).
Формат атрибута: app:layout_constraint[Source]_to[Target]Of="[id]" — де Source — край, що прив'язується (Left, Right, Top, Bottom, Start, End, Baseline), Target — цільовий край. Приклад: app:layout_constraintTop_toBottomOf="@+id/header" означає «верхня межа поточного елемента прив'язана до нижньої межі елемента header». Для прив'язки до батька використовується id parent.
Bias (зміщення) — параметр, що працює за наявності протилежних констрейнтів (left + right або top + bottom). Значення від 0 до 1: 0 — притиснутий до лівого/верхнього краю, 0.5 — по центру, 1 — до правого/нижнього. Атрибути: layout_constraintHorizontal_bias (0.0–1.0) та layout_constraintVertical_bias. Margin задається стандартними android:layout_margin*, але констрейнт та margin працюють незалежно: margin — це відступ від констрейнта, а не від сусіднього View.
З ConstraintLayout 1.1+ з'явилася підтримка відсоткових розмірів через layout_constraintWidth_percent та layout_constraintHeight_percent. Значення 0.3 означає 30% від ширини/висоти батька. У поєднанні з bias це дозволяє створювати адаптивні макети без програмування.
Chain (ланцюг) — це група з двох або більше View, пов'язаних двосторонніми констрейнтами (A прив'язаний до B, B до A). Ланцюги автоматично розподіляють простір між елементами за одним із режимів: spread (рівномірно, з урахуванням margin), spread_inside (рівномірно, крайові елементи без відступу від країв), packed (елементи притиснуті один до одного із загальним bias). Режим задається атрибутом app:layout_constraintHorizontal_chainStyle або layout_constraintVertical_chainStyle.
Guideline (напрямна) — допоміжний View, невидимий у рантаймі, що задає лінію для прив'язки. Guideline може бути горизонтальним або вертикальним, позиціонованим у dp, відсотках (app:layout_constraintGuide_percent) або з відступом від краю (app:layout_constraintGuide_begin/end). Guidelines незамінні для адаптивних макетів — наприклад, для поділу екрана на дві рівні половини незалежно від розміру пристрою.
За даними Google I/O 2017, ланцюги з spread_inside на 15–20% продуктивніші за вкладені LinearLayout з weight, оскільки уникають подвійного проходу measure, необхідного для розрахунку weight.
Barrier (бар'єр) — віртуальний View, який динамічно підлаштовує свою позицію під розмір групи елементів. На відміну від Guideline з фіксованою позицією, Barrier «штовхається» найширшим елементом групи. Наприклад, якщо у вас є заголовок та опис невідомої довжини, Barrier, прив'язаний до правого краю найширшого тексту, дозволяє розмістити іконку одразу після них. Атрибути: app:barrierDirection (left, right, top, bottom, start, end) та app:constraint_referenced_ids (список id через кому).
Group — віртуальний контейнер, який керує видимістю (visibility) кількох View одночасно. Замість того щоб викликати setVisibility для кожного елемента окремо, достатньо змінити visibility однієї Group. Group не впливає на позиціонування — лише на видимість. Flow — віртуальний помічник для створення «плинних» макетів: елементи автоматично переносяться на новий рядок/колонку при нестачі місця, як текст в абзаці. Flow підтримує wrapMode: none, chain та aligned.
Ці інструменти (Barrier, Group, Flow, Guideline) називаються віртуальними помічниками, тому що вони не є View у класичному сенсі — вони не займають місця в ієрархії та не беруть участі у фокусі чи тач-подіях. Їхня мета — спростити підтримку складних макетів без додавання вкладених контейнерів.
Проста форма входу з полем email, паролем та кнопкою. Всі елементи прив'язані до parent, крім кнопки — вона під полем пароля. Використовується flat hierarchy — всі три елементи на одному рівні.
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/email_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp"
android:layout_marginHorizontal="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/email_input"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp" />
<Button
android:id="@+id/login_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/password_input"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="24dp"
android:layout_marginHorizontal="16dp"
android:text="Увійти" />
</androidx.constraintlayout.widget.ConstraintLayout>
Всі елементи мають ширину 0dp (match_constraint), тобто розтягуються від start до end констрейнта з урахуванням horizontal margin. Це аналог match_parent з відступами, але без вкладеності.
Три кнопки, рівномірно розподілені по горизонталі з відступами від країв. Ланцюг spread_inside розміщує крайові кнопки по краях, а середню — по центру між ними.
<Button
android:id="@+id/btn_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_center"
android:text="Лівий" />
<Button
android:id="@+id/btn_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/btn_left"
app:layout_constraintRight_toLeftOf="@+id/btn_right"
android:text="Центр" />
<Button
android:id="@+id/btn_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/btn_center"
app:layout_constraintRight_toRightOf="parent"
android:text="Правий" />
Ланцюг створюється автоматично, коли елементи мають двосторонні констрейти. Режим spread_inside задається на будь-якому елементі ланцюга через app:layout_constraintHorizontal_chainStyle="spread_inside". Це позбавляє необхідності використовувати LinearLayout з weightSum та layout_weight.
Створення двох рівних колонок через вертикальну Guideline на 50%. Лівий елемент прив'язаний до left parent і правим краєм до guideline; правий — лівим краєм до guideline і до right parent.
<androidx.constraintlayout.widget.Guideline
android:id="@+id/gl_midpoint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<TextView
android:id="@+id/left_card"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/gl_midpoint"
android:layout_margin="8dp"
android:background="@color/card_background" />
<TextView
android:id="@+id/right_card"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/gl_midpoint"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="8dp"
android:background="@color/card_background" />
Guideline з відсотком 0.5 автоматично адаптується під ширину екрана. На планшеті та телефоні співвідношення колонок залишиться 50/50. Для лівої/правої номенклатури використовуйте start/end атрибути для RTL-сумісності.
Таблиця порівняння трьох основних ViewGroup для Android-розробки: ConstraintLayout, LinearLayout та RelativeLayout. Критерії: гнучкість, продуктивність, складність коду та області застосування.
| Характеристика | ConstraintLayout | LinearLayout | RelativeLayout |
|---|---|---|---|
| Вкладеність | Пласка (один рівень) | Вимагає вкладеності для складних макетів | Один рівень, але обмежена гнучкість |
| Продуктивність measure | 2 проходи (~40% швидше) | 4+ проходи при weight | 2 проходи |
| Відсоткові розміри | Так (guide_percent, width_percent) | Тільки через weight/frame | Ні |
| RTL-підтримка | Вбудована (start/end) | Вбудована | Через start/end (API 17+) |
| Barrier/Group/Flow | Так (віртуальні помічники) | Ні | Ні |
| MotionLayout анімації | Так | Ні | Ні |
| Коли використовувати | Всі складні макети, екрани з >5 елементами | Прості однонаправлені списки, рядки з кнопками | Прості відносні макети (з Legacy-коду) |
За даними Android Vitals (Google, 2025), додатки, що використовують ConstraintLayout як основний контейнер, в середньому показують на 18% менше jank-кадрів при відображенні складних екранів порівняно з додатками на вкладених LinearLayout. В IT Sectr ми перейшли на ConstraintLayout як стандарт для всіх XML-макетів у 2018 році — це скоротило середню глибину ієрархії екранів з 4.2 до 1.8 рівня та прискорило розробку нових форм на 25%.
Часто задавані питання
match_parent у ConstraintLayout працює як зазвичай — розтягує View до розміру батька. 0dp (match_constraint) означає, що розмір View обчислюється з констрейнтів: якщо задані left та right констрейти з margin, ширина = parent — marginLeft — marginRight. Різниця в поведінці: match_parent ігнорує bias і може вилізти за межі при анімації; match_constraint коректно враховує всі обмеження та рекомендується Google як основний режим для ConstraintLayout.
Використовуйте комбінацію: відсоткові розміри (layout_constraintWidth_percent) для елементів, які повинні займати частку екрана; Guideline з відсотками для розбивки екрана на зони; Barrier для позиціонування відносно динамічного контенту; Flow з wrapMode для перенесення карток на новий рядок. Альтернативний підхід — використовувати SlidingPaneLayout у зв'язці з ConstraintLayout для master-detail-інтерфейсів на планшетах.
Jetpack Compose не використовує ConstraintLayout як ViewGroup, але надає ConstraintLayout compose-версію (androidx.constraintlayout:constraintlayout-compose) з тим же API на Kotlin DSL: createRefFor(), constrainAs(), linkTo(), chain(), guideFrom(). Це корисно для складних макетів, які простіше описати через констрейти, ніж через Column/Row. Однак у Compose рекомендується починати з Column/Row/Box і переходити до ConstraintLayout тільки коли потрібні складні відносні позиціонування.
В Android Studio відкрийте Layout Inspector (Tools → Layout Inspector), виберіть запущений додаток і наведіть на проблемний елемент. Ви побачите всі констрейти, margin, padding та bias у тривимірному представленні. Для XML використовуйте панель Design у редакторі макетів — вона підсвічує конфлікти констрейнтів жовтим, а відсутність констрейнтів — червоним. У коді перевірте, що кожен View має два перпендикулярних констрейти, інакше елемент опиниться в (0,0).
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також