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>
  1. Прив’язка до батьківського контейнера:
    • Елемент titleText закріплений до верхньої частини контейнера через:
      app:layout_constraintTop_toTopOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      
  2. Прив’язка між елементами:
    • Кнопка submitButton прив’язана до нижньої частини inputField через:
      app:layout_constraintTop_toBottomOf="@id/inputField"
      
  3. Розмір із використанням нуля (0dp):
    • Поле введення inputField займає всю доступну ширину, завдяки:
      android:layout_width="0dp"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      
  4. Пропорційне розташування (bias):
    • Текст опису descriptionText розташований ближче до лівого краю через:
      app:layout_constraintHorizontalBias="0.3"
      
  5. Вирівнювання елементів між собою:
    • Елемент footerText вирівняний до низу екрану:
      app:layout_constraintBottom_toBottomOf="parent"
      
  6. Розтягнення та обмеження розмірів:
    • Поля, такі як inputField і descriptionText, розтягуються між прив’язками start і end, забезпечуючи адаптивний інтерфейс.