В Android-разработке представлены самые разные способы конструирования интерфейса, но ConstraintLayout – один из самых мощных и гибких. Лично я его использую 90% времени. Он позволяет легко управлять расположением компонентов. В этой статье мы подробно рассмотрим, как использовать ConstraintLayout для разработки интерфейса на Android.
Создание ConstraintLayout
ConstraintLayout – это контейнер для размещения и управления компонентами интерфейса. Это достаточно замысловатый макет, более сложный, чем, к примеру, FrameLayout или LinearLayout.
ConstraintLayout, как и другие Layout, рассказывает приложению, как должны располагаться кнопки, картинки, текстовые надписи. Во время выполнения на конкретном устройстве происходят просчёты, определяющие точные размеры и местоположение компонентов. Благодаря этому макеты адаптируются к разным размерам экрана и устройствам.
Прежде чем начать работать с ConstraintLayout, убедитесь, что вы добавили библиотеку ConstraintLayout в файле build.gradle вашего проекта:
1 |
implementation 'androidx.constraintlayout:constraintlayout:2.1.0' |
Стоит отметить, что есть другая, более древняя версия ConstraintLayout. Здесь я привожу более новую, из AndroidX. Сейчас она по умолчанию применяется в создаваемых проектах IntelliJ IDEA. То есть вам задумываться об этом не нужно.
AndroidX – это современная библиотека, которая заменяет устаревший пакет поддержки Android (Support Library). Она предоставляет более актуальные библиотеки. Переход на AndroidX рекомендуется всем разработчикам, так как старая Support Library больше не обновляется и развивается. Переход на AndroidX желателен, но не обязателен. Иногда разработчики хотят использовать старую версию ConstraintLayout из устаревшей библиотеки Android. Это может потребоваться, например, если приложение зависит от определённой функциональности, доступной только в старой версии. А ведь функциональность потихоньку меняется от версии к версии Android API.
Здесь и дальше будем пользоваться предлагаемыми нам нововведениями – то есть компонентами из библиотеки AndroidX.
Для начала создайте новый XML-файл разметки в папке res/layout вашего проекта. Затем выберите ConstraintLayout в качестве корневого элемента:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/mainLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> </androidx.constraintlayout.widget.ConstraintLayout> |
Этого же результата можно добиться, если в IDE выбрать создание New -> Activity. Тогда среда разработки создаст для вас и java-файл для Activity, и файл XML-разметки её интерфейса.
Итак, ConstraintLayout создан. Пока что он чрезвычайно пуст – не содержит ни одного компонента внутри себя. У него есть всякие свойства: android:id (идентификатор, то есть уникальное имя компонента), android:layout_width (ширина компонента), android:layout_height (высота компонента), tools:context (контекст, то есть какая Activity будет использовать этот макет). Насчёт xmlns:app, xmlns:android и xmlns:tools можно не задумываться. Они заполняются автоматически средой разработки, предоставляя доступ к необходимым инструментам.
Привязка к компонентам
Теперь, когда у вас есть ConstraintLayout, вы можете добавлять в него компоненты, такие как кнопки, текстовые поля и изображения. Разберём пример размещения кнопки. Перетащим кнопку из панели компонентов прямо в ConstraintLayout.
Ага, видно, что IntelliJ IDEA ругается. Около компонента button есть красный кружочек с восклицательным знаком. Это ничего: мы скоро всё поправим. Давайте посмотрим причину недовольства среды разработки, перейдя в режим “Split“.
Видно, что проблема заключается в том, что компонент не снабжён ограничениями. Это фишка ConstraintLayout – ограничения. Суть в том, что у каждого компонента должно быть хотя бы по одному ограничению как по горизонтали, так и по вертикали. Максимум – по два. По горизонтали компонент может быть привязан к началу какого-либо компонента (слева), к его концу (справа) или к обеим этим сторонам. По вертикали компонент может быть привязан к верху компонента, к низу или к обоим направлениям.
Ещё лучше это понять, увидев наглядно, что и куда. Задавать ограничения можно как в режиме дизайна, перетаскивая мышкой край компонента к краю контейнера, так и прописывая свойства в коде разметки XML. Давайте минимально привяжем кнопку так, чтобы ошибка исчезла. Сделаем привязку к верху и к началу контейнера ConstraintLayout.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/mainLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:text="Button" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> |
В итоге кнопка Button размещена слева сверху в ConstraintLayout. Всё достаточно прозаично – однако ошибка нивелирована. Минимальные привязки есть, Button знает, куда ей себя девать. Отличная работа.
Добавим ещё одну строчку: привязку к концу (правой стороне) контейнера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/mainLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:text="Button" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Вуаля! Кнопка привязана и к началу, и к концу контейнера. Теперь она находится посередине.
Однако сверху она что-то слишком сильно прилегает, не правда ли? Давайте пропишем небольшой отступ в 20dp.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/mainLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:text="Button" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="20dp"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Отлично – отступ в 20dp хорошо скрашивает картину. Теперь кнопка выглядит вполне себе уравновешенно.
Правда, ей наверняка одиноко. Разместим сюда ещё текст (TextView). Однако вместо привязок к контейнеру ConstraintLayout, привяжем TextView к кнопке.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/mainLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:text="Button" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="20dp"/> <TextView android:id="@+id/textView" android:text="Beauty Of Java" android:textSize="20sp" android:textColor="@color/black" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/button" app:layout_constraintStart_toStartOf="@id/button" app:layout_constraintEnd_toEndOf="@id/button" android:layout_marginTop="30dp"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Как видите, TextView знать не знает, как ему надо бы ориентироваться относительно ConstraintLayout. Зато он прекрасно понимает, что его ближайший друг – Button – станет для него маяком во тьме разметки интерфейса Android-приложения. В итоге TextView привязался верхом к низу Button, а также началом к началу и концом и концу.
HorizontalBias и VerticalBias
HorizontalBias и VerticalBias – это параметры, используемые в макете ConstraintLayout для управления вертикальным и горизонтальным выравниванием. Они позволяют указать, каким образом компонент будет смещаться внутри ConstraintLayout по вертикали и горизонтали относительно указанных ограничений.
Например, кнопка Button располагается по центру. Но это скучно. Я хочу поместить её так, чтобы она была находилась на 25% от начала (и, соответственно, на 75% от конца). Тогда нужно указать HorizontalBias как 0.25.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/mainLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:text="Button" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="20dp" app:layout_constraintHorizontal_bias="0.25"/> <TextView android:id="@+id/textView" android:text="Beauty Of Java" android:textSize="20sp" android:textColor="@color/black" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/button" app:layout_constraintStart_toStartOf="@id/button" app:layout_constraintEnd_toEndOf="@id/button" android:layout_marginTop="30dp"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Вот кнопочка и сместилась. Теперь её центр расположен в отметке 25% по горизонтальному отрезку между началом и концом контейнера ConstraintLayout. Кстати, сместился и TextView, так как он полностью опирается на расположение кнопки. Меняем расположение кнопки – автоматически меняется расположение текста.
HorizontalBias и VerticalBias можно задать только там, где задано оба направления привязки. Например, когда по вертикали есть привязка и к началу, и к концу. Или когда по горизонтали компонент привязан как к верху, так и к низу.
Если указать HorizontalBias или VerticalBias как 0.5, то ничего в картине мира интерфейса не поменяется. Происходит так потому, что при наличии обеих привязок компонент и так располагается по центру – то есть на 50% от обоих сторон.
Соотношение сторон компонентов
Есть такая фишка у ConstraintLayout, которая позволяет задать отношение между высотой и шириной компонентов. Это бывает очень удобно. Например, вы знаете, что картинка должна быть растянута по ширине во всю ширину экрана. Но высоту растягивать по всей высоте экрана вы не хотите. Вам необходимо, чтобы высота компонента стала равна ширине. То есть задать между ними соотношение 1:1. Это легко делается при помощи атрибута app:layout_constraintDimensionRatio. Этот атрибут рассчитывает неизвестную сторону из известной с помощью заданной вами пропорции. Например – 1:1, то есть одинаковые стороны. Неизвестной стороне необходимо задать размер 0dp.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/mainLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintDimensionRatio="1:1" android:scaleType="fitXY" android:src="@drawable/beautiful_blue_background" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Так как android:layout_width=”match_parent”, компонент Image занял всю ширину. В свою очередь, высота просто использовала соотношение 1:1 и стала равна ширине. Так получилось потому, что в атрибуте высоты стоит значение 0 (android:layout_height=”0dp”), а в атрибуте соотношения сторон – 1:1 (app:layout_constraintDimensionRatio=”1:1″).
Обратите внимание, что благодаря атрибуту android:scaleType=”fitXY” картинка растягивается на весь размер компонента Image.
Необязательно приравнивать высоту к ширине или ширину к высоте. Соотношения можно задавать самые разные. Это может быть 1:2, 2:7 или вообще 25:849. Например, здесь высота будет равна половине ширины:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/mainLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintDimensionRatio="2:1" android:scaleType="fitXY" android:src="@drawable/beautiful_blue_background" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Здесь компонент немного сжался по высоте. Да и не немного – в целых два раза. То есть соотношение 2:1 говорит о том, что одна ширина равна двум высотам. Другими словами, ширина вдвое больше высоты.
Необязательно вычислять высоту из ширины. Можно делать и наоборот – задавая высоту, оставлять в 0dp ширину и тоже прописывать соотношение сторон. Тогда работать это будет в обратную сторону.
Размеры в процентах
Не раз и не два передо мной стояла такая ситуация, когда уместнее всего было задавать размер в процентах от размера экрана – ширины или высоты. У нас есть match_parent, да, но он подходит исключительно для ситуации 100%. А как насчёт 50%? 25%? 78%?
Для таких особых случаев ConstraintLayout приготовил нам особую вещь – layout_constraintWidth_percent и layout_constraintHeight_percent. Вот обычная, ничем не примечательная золотая полоска, заполняющая всю ширину и 20dp высоты:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <ImageView android:layout_width="match_parent" android:layout_height="20dp" android:src="@color/gold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Чтобы сделать заполнение не на 100%, а на 90% по ширине, просто занулим layout_width и выставим параметр layout_constraintWidth_percent в 0.9.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <ImageView android:layout_width="0dp" android:layout_height="20dp" app:layout_constraintWidth_percent="0.9" android:src="@color/gold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> |
С высотой всё работает точно так же, только изменять нужно будет параметры layout_height и layout_constraintHeight_percent.
Вот мы и познакомились поближе с ConstraintLayout в Android. Используйте ограничения, предлагаемые этим контейнером, чтобы создавать интересные макеты интерфейсов. ConstraintLayout невероятно гибок, что позволяет мне использовать его в качестве каркаса в 90% случаев. Чтобы разобраться в теме, пробуйте изменять всё, что видите – будь то значение свойства start_ToStartOf или horizontalBias. После создания макета интерфейса вас ждёт увлекательное управление этими компонентами из Java-кода.
Уведомление: Как добавить музыку и звуки в приложение Android