Разбираемся, что такое 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 — layout nesting. Каждый вложенный 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 (меньше рекурсивных вызовов), более предсказуемое поведение при изменении размеров экрана.
Констрейнт (constraint) — это связь между краем одного 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) называются виртуальными помощниками (virtual helpers), потому что они не являются 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также