ConstraintLayout
ConstraintLayout — це контейнер, який дозволяє створювати гнучкі та масштабовані візуальні інтерфейси.
Для позиціонування елемента всередині ConstraintLayout необхідно вказати обмеження (constraints). Існує декілька типів обмежень. Зокрема, для встановлення позиції відносно певного елемента використовуються наступні обмеження:
layout_constraintLeft_toLeftOf: ліва межа позиціонується відносно лівої межі іншого елементаlayout_constraintLeft_toRightOf: ліва межа позиціонується відносно правої межі іншого елементаlayout_constraintRight_toLeftOf: права межа позиціонується відносно лівої межі іншого елементаlayout_constraintRight_toRightOf: права межа позиціонується відносно правої межі іншого елементаlayout_constraintTop_toTopOf: верхня межа позиціонується відносно верхньої межі іншого елементаlayout_constraintTop_toBottomOf: верхня межа позиціонується відносно нижньої межі іншого елементаlayout_constraintBottom_toBottomOf: нижня межа позиціонується відносно нижньої межі іншого елементаlayout_constraintBottom_toTopOf: нижня межа позиціонується відносно верхньої межі іншого елементаlayout_constraintBaseline_toBaselineOf: базова лінія позиціонується відносно базової лінії іншого елементаlayout_constraintStart_toEndOf: елемент починається там, де закінчується інший елементlayout_constraintStart_toStartOf: елемент починається там, де починається інший елементlayout_constraintEnd_toStartOf: елемент закінчується там, де починається інший елементlayout_constraintEnd_toEndOf: елемент закінчується там, де закінчується інший елемент
Можливо, щодо чотирьох останніх властивостей виникло певне непорозуміння, що саме мається на увазі під початком або завершенням елемента. Справа в тому, що деякі мови (наприклад, арабська або фарсі) мають правосторонню орієнтацію, тобто символи йдуть справа наліво, а не зліва направо, як у європейських мовах. І залежно від поточної орієнтації — лівостороння чи правостороння — буде змінюватися те, де саме початок, а де завершення елемента. Наприклад, при лівосторонній орієнтації початок — зліва, а завершення — справа, тому атрибут layout_constraintStart_toEndOf фактично буде аналогічний атрибуту layout_constraintLeft_toRightOf. А при правосторонній орієнтації — атрибуту layout_constraintRight_toLeftOf, оскільки початок справа, а завершення — зліва.
Кожне обмеження встановлює позиціонування елемента або по горизонталі, або по вертикалі. І для визначення позиції елемента в ConstraintLayout необхідно вказати щонайменше одне обмеження по горизонталі та одне обмеження по вертикалі.
Позиціонування може здійснюватися відносно меж самого контейнера ConstraintLayout (у цьому випадку обмеження має значення parent), або ж відносно будь-якого іншого елемента всередині ConstraintLayout, тоді як значення обмеження вказується id цього елемента.
Простий приклад:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="30sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
У цьому випадку для елемента TextView встановлено два обмеження: одне по горизонталі (app:layout_constraintLeft_toLeftOf="parent"), друге — по вертикалі (app:layout_constraintTop_toTopOf="parent"). Обидва обмеження встановлюються відносно контейнера ConstraintLayout, тому вони приймають значення parent, тобто ConstraintLayout.
Обмеження app:layout_constraintLeft_toLeftOf="parent" встановлює ліву межу TextView біля лівої межі контейнера.
Обмеження app:layout_constraintTop_toTopOf="parent" встановлює верхню межу TextView біля верхньої межі контейнера.
У результаті TextView буде розташовуватися у верхньому лівому куті контейнера.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="30sp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Варто звернути увагу, що всі ці атрибути обмежень беруть із простору імен "http://schemas.android.com/apk/res-auto", який проектується на префікс app.
Якщо необхідно встановити обмеження щодо іншого елемента, то необхідно вказати id цього елемента:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/editText"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:hint="Введите Email"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Отправить"
app:layout_constraintLeft_toRightOf="@id/editText"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Тут для текстового поля введення EditText встановлюються два обмеження щодо батьківського контейнера ConstraintLayout, тож обмеження мають значення parent, а сам EditText вирівнюється по лівій і верхній межі контейнера. Верхня межа кнопки Button також вирівнюється по верхній межі контейнера. А ось ліва межа кнопки вирівнюється по правій межі EditText. Для цього як значення атрибута передається id EditText:
app:layout_constraintLeft_toRightOf="@id/editText"
Подібним чином можна складати різні комбінації атрибутів для визначення потрібного нам позиціонування. Наприклад, змінимо код кнопки:
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Отправить"
app:layout_constraintLeft_toRightOf="@id/editText"
app:layout_constraintTop_toBottomOf="@id/editText" />
У цьому випадку верхня межа кнопки вирівнюється по нижній межі EditText
Ба більше, ми можемо позиціонувати обидва елементи один відносно іншого:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Введите Email"
app:layout_constraintRight_toLeftOf="@+id/button"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Отправить"
app:layout_constraintLeft_toRightOf="@id/editText"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Позиціонування в центрі
Якщо необхідно розташувати елемент у центрі контейнера по вертикалі, то треба використовувати пару атрибутів
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
Якщо необхідно розташувати елемент у центрі контейнера по горизонталі, то треба використовувати таку пару атрибутів
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
Відповідно для розташування в центрі контейнера по вертикалі та горизонталі треба застосувати всі вище зазначені чотири атрибути:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello Android"
android:textSize="30sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Зсув
Якщо елементи розташовані по центру, ConstraintLayout дає змогу їх зрушувати по горизонталі та по вертикалі. Для зсуву по горизонталі застосовується атрибут layout_constraintHorizontal_bias, а для зсуву по вертикалі - атрибут layout_constraintVertical_bias. Як значення вони приймають число з плаваючою крапкою від 0 до 1. Значення за замовчуванням - 0.5 (розташування по центру). Наприклад:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Top TextView"
android:textSize="30sp"
android:background="#e0e0e0"
app:layout_constraintHorizontal_bias="0.2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Bottom TextView"
android:textSize="30sp"
android:background="#e0e0e0"
app:layout_constraintHorizontal_bias=".9"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Перший TextView зсувається на 20% від лівої межі контейнера (значення за замовчуванням - 0.5, тому при значенні 0.2 елемент зсувається вліво). Другий TextView зсувається на 90% від лівої межі контейнера. Наприклад, значення 1 означало б, що елемент присунутий до правої межі, а значення 0 - до лівої
Аналогічно працює атрибут layout_constraintVertical_bias, який зсуває по вертикалі.
Приклад
<?xml version="1.0" encoding="utf-8"?>
<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"
android:padding="16dp">
<!-- Заголовок, прикріплений до верхньої частини батьківського контейнера -->
<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ConstraintLayout Demo"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginBottom="16dp" />
<!-- Поле введення тексту, вирівняне по центру горизонтально -->
<EditText
android:id="@+id/inputField"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="Введіть текст"
app:layout_constraintTop_toBottomOf="@id/titleText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp" />
<!-- Кнопка з прив’язкою до нижньої частини поля введення -->
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Надіслати"
app:layout_constraintTop_toBottomOf="@id/inputField"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginBottom="16dp" />
<!-- Іконка, вирівняна по правому краю з пропорційним розміром -->
<ImageView
android:id="@+id/iconImage"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_launcher_foreground"
app:layout_constraintTop_toBottomOf="@id/submitButton"
app:layout_constraintEnd_toEndOf="parent"
android:layout_margin="8dp" />
<!-- Текст, прив'язаний до іконки з використанням bias -->
<TextView
android:id="@+id/descriptionText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Це приклад використання ConstraintLayout."
android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@id/submitButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/iconImage"
app:layout_constraintHorizontalBias="0.3" />
<!-- Нижній текст, вирівняний до низу екрану -->
<TextView
android:id="@+id/footerText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Footer текст"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
- Прив’язка до батьківського контейнера:
- Елемент
titleTextзакріплений до верхньої частини контейнера через:app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
- Елемент
- Прив’язка між елементами:
- Кнопка
submitButtonприв’язана до нижньої частиниinputFieldчерез:app:layout_constraintTop_toBottomOf="@id/inputField"
- Кнопка
- Розмір із використанням нуля (
0dp):- Поле введення
inputFieldзаймає всю доступну ширину, завдяки:android:layout_width="0dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
- Поле введення
- Пропорційне розташування (bias):
- Текст опису
descriptionTextрозташований ближче до лівого краю через:app:layout_constraintHorizontalBias="0.3"
- Текст опису
- Вирівнювання елементів між собою:
- Елемент
footerTextвирівняний до низу екрану:app:layout_constraintBottom_toBottomOf="parent"
- Елемент
- Розтягнення та обмеження розмірів:
- Поля, такі як
inputFieldіdescriptionText, розтягуються між прив’язкамиstartіend, забезпечуючи адаптивний інтерфейс.
- Поля, такі як
