Перейти к содержимому

Match parent wrap content что это

  • автор:

Match parent wrap content что это

Все визуальные элеметы, которые мы используем в приложении, как правило, упорядочиваются на экране с помощью контейнеров. В Android подобными контейнерами служат такие классы как RelativeLayout, LinearLayout, GridLayout, TableLayout, ConstraintLayout, FrameLayout. Все они по разному располагают элементы и управляют ими, но есть некоторые общие моменты при компоновке визуальных компонентов, которые мы сейчас рассмотрим.

Для организации элементов внутри контейнера используются параметры разметки. Для их задания в файле xml используются атрибуты, которые начинаются с префикса layout_ . В частности, к таким параметрам относятся атрибуты layout_height и layout_width , которые используются для установки размеров и могут использовать одну из следующих опций:

  • Растяжение по всей ширине или высоте контейнера с помощью значения match_parent (для всех контейнеров кроме ConstraintLayout) или 0dp (для ConstraintLayout)
  • Растяжение элемента до тех границ, которые достаточны, чтобы вместить все его содержимое с помощью значения wrap_content
  • Точные размеры элемента, например 96 dp

match_parent

Установка значения match_parent позволяет растянуть элемент по всей ширине или высоте контейнера. Стоит отметить, что данное значение применяется ко всем контейнерам, кроме ConstraintLayout. Например, рястянем элемент TextView по всей ширине и высоте контейнера LinearLayout:

Контейнер самого верхнего уровня, в качестве которого в данном случае выступает LinearLayout , для высоты и ширины имеет значение match_parent , то есть он будет заполнять всю область для activity — как правило, весь экран.

И TextView также принимает подобные атрибуты. Значение android:layout_width=»match_parent» обеспечивает растяжение по ширине, а android:layout_height=»match_parent» — по вертикали. Для наглядности в TextView применяет атрибут android:background , который представляет фон и в данном случае окрашивает элемент в цвет «#e0e0e0», благодаря чему мы можем увидеть занимаемую им область.

Высота и длина match_parent в Android

Следует учитывать, что значение match_parent можно применять почти во всех встроенных контейнерах, типа LinearLayout или RelativeLayout и их элементах. Однако match_parent не рекомендуется применять к элементам внутри ConstraintLayout. Вместо «match_parent» в ConstraintLayout можно использовать значение 0dp , чтобы растянуть элемент по горизонтали или вертикали:

Стоит отметить, что ConstraintLayout сама также растягивается по ширине и высоте экрана с помощью значения «match_parent» в атрибутах layout_width и android:layout_height , но к вложенным элементам это значение не рекомендуется применять.

Поскольку ConstraintLayout имеет некоторые особенности при установке размеров, то более подробно работа с размерами элементов именно в ConstraintLayout раскрыта более подробно в одной из следующих тем.

wrap_content

Значение wrap_content устанавливает те значения для ширины или высоты, которые необходимы, чтобы разместить на экране содержимое элемента:

Здесь элемент TextView растягивается до тех значений, которые достаточны для размещения его текста.

Высота и длина wrap_content в Android Studio

Установка точных значений

Также мы можем установить точные значения:

Установка высоты и длины в Android Studio

Кроме того, можно комбинировать несколько значений, например, растянуть по ширине содержимого и установить точные значения для высоты:

Если для установки ширины и длины используется значение wrap_content , то мы можем дополнительно ограничить минимальные и максимальные значения с помощью атрибутов minWidth/maxWidth и minHeight/maxHeight :

В этом случае ширина TextView будет такой, которая достаточна для вмещения текста, но не больше значения maxWidth и не меньше значения minWidth. То же самое для установки высоты.

Программная установка ширины и высоты

Если элемент, к примеру, тот же TextView создается в коде java, то для установки высоты и ширины можно использовать метод setLayoutParams() . Так, изменим код MainActivity:

package com.example.viewapp; import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); ConstraintLayout constraintLayout = new ConstraintLayout(this); TextView textView = new TextView(this); textView.setText("Hello Android"); textView.setTextSize(26); // устанавливаем параметры размеров и расположение элемента ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams (ConstraintLayout.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.WRAP_CONTENT); // эквивалент app:layout_constraintLeft_toLeftOf="parent" layoutParams.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID; // эквивалент app:layout_constraintTop_toTopOf="parent" layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; // устанавливаем параметры для textView textView.setLayoutParams(layoutParams); // добавляем TextView в ConstraintLayout constraintLayout.addView(textView); setContentView(constraintLayout); >>

В метод setLayoutParams() передается объект ViewGroup.LayoutParams . Этот объект инициализируется двумя параметрами: шириной и высотой. Для указания ширины и высоты можно использовать одну из констант ViewGroup.LayoutParams.WRAP_CONTENT или ViewGroup.LayoutParams.MATCH_PARENT (в случае с LinearLayout или RelativeLayout).

Поскольку в данном случае мы имеем дело с контейнером ConstraintLayout, то для установки размеров применяется значение ConstraintLayout.LayoutParams.WRAP_CONTENT . В реальности класс androidx.constraintlayout.widget.ConstraintLayout.LayoutParams , который предоставляет это значение, наследуется от android.view.ViewGroup.LayoutParams

Программная установка высоты и длины в Android и ViewGroup.LayoutParams.MATCH_PARENT

Также мы можем передать точные значения или комбинировать типы значений:

ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams (ConstraintLayout.LayoutParams.WRAP_CONTENT, 200);

Работа с ConstraintLayout через XML-разметку

ConstraintLayout

Привет! Меня зовут Гавриил, я Android-лид Touch Instinct.

В марте Google выкатил релизное обновление ConstraintLayout. Презентовали его еще на прошлом Google I/O. С того момента прошел почти год, ConstraintLayout стал лучше, быстрее и оброс новыми возможностями. Например, приятно, что появилась возможность объединения элементов в цепи — это позволяет использовать ConstraintLayout вместо LinearLayout.

О всех новых и старых возможностях ConstraintLayout я и постараюсь рассказать в этой статье. Предупрежу сразу — статья будет длинная и других семи частей не будет. Про визуальный редактор ConstraintLayout в статье не будет ни слова — только XML-разметка (по старинке) и немного кода.

Навигация:

Добавляем в проект

  1. Обновляем версию Android Studio до 2.3;
  2. Проверяем, что установлена последняя версия ConstraintLayout — это можно посмотреть в Android Studio → Settings(Preferences) → Appearance & Behavior → System Settings → Android SDK → SDK Tools → Support Repository ;
  3. Добавляем dependency в build.gradle модуля проекта:
dependencies

Constraints. Привязываем элементы друг к другу

Constraints — это линии, на основе которых располагается view внутри ConstraintLayout. Constraints могут быть привязаны к сторонам самого ConstraintLayout или к сторонам других view внутри ConstraintLayout. Constraints можно разделить на вертикальные и горизонтальные.

  • правой стороны (Right), левой стороны (Left);
  • начальной стороны (Start), конечной стороны (End).
  • верхней стороны (Top), нижней стороны (Bottom);
  • базовой линии (Baseline).

Вертикальное и горизонтальное constraints друг с другом не связаны.

Напомню, что Baseline — это линия выравнивания контента элемента. Пример — для TextView это линия строки, на которой пишется текст. Если у view выставлен Baseline сonstraint, то базовая линия элемента будет находиться на уровне базовой линии view, к которой привязан сonstraint.

Для начала, проще всего рассматривать сonstraints, как стороны view. То есть можно, например, привязать левую сторону view B к правой стороне view A — тогда view B будет располагаться справа от view A .

Общий формат атрибутов для привязки сonstraint выглядит следующим образом:

 app:layout_constraint_toOf=""
  • X — сonstraint привязываемой view;
  • Yсторона view, к которой привязываются;
  • Z — id view, к которой привязываются, или parent , если привязать нужно к стороне ContraintLayout.

Пример

Основные правила привязки сторон:

  • привязывать между собой можно только Start и End , Left и Right , Top и Bottom . То есть, нельзя, например, привязать Left к Start или Baseline к Top;
  • Baseline можно привязать только к Baseline другой view;
  • при привязке Start/End игнорируются привязки Left/Right;
  • при привязке Baseline игнорируются привязки Top/Bottom;
  • не привязывайте view с внешней стороны ConstraintLayout, например, layout_constraintRight_toLeftOf=»parent» . ConstraintLayout обработает такую привязку, но как себя при этом поведет, сложно предсказать.

Задаем размеры View

Чтобы задать размеры view, используются обязательные атрибуты layout_width и layout_height , и необязательные атрибуты layout_constraintWidth_default и layout_constraintHeight_default .

Значение атрибутов layout_constraintWidth_default и layout_constraintHeight_default по умолчанию равно spread .

Размер view могут быть указан, как (на примере высоты):

  • layout_height=»100dp» — обозначим, как fixed_size. View будет указанного размера;
  • layout_height=»wrap_content» — обозначим, как any_size. Размер вычисляется самой view, может быть любым;
  • layout_height=»0dp» + layout_constraintHeight_default=»spread» — обозначим, как match_constraint_spread. Размер view будет равен расстоянию между constraints. Для высоты, например, это расстояние между верхним constraint и нижним constraint;
  • layout_height=»0dp» + layout_constraintHeight_default=»wrap» — обозначим, как match_constraint_wrap. Размер вычисляется самой view, но не может выйти за рамки constraints.

Важно! Указывать размер match_parent или fill_parent запрещено. Чтобы размер view совпадал с размерами ConstraintLayout, достаточно просто привязать constraints к сторонам ConstraintLayot и использовать размер match_constraint_spread.

Если указан размер match_constraint_wrap или match_constraint_spread, стоит учесть, что:

  • чтобы такой тип размера работал корректно, у view должны быть привязаны два constraint: для ширины это Left и Right или Start и End . Для высоты — Top и Bottom ;
  • размер view не может выйти за рамки constraints;
  • можно выставить минимальный и максимальный размер view в рамках constraints. Для этого используются атрибуты layout_constraintWidth_min , layout_constraintHeight_min , layout_constraintWidth_max , layout_constraintHeight_max ;
  • не стоит выставлять такой тип размера для высоты, если у view привязан Baseline constraint — вероятно, высота элемента будет рассчитываться неверно.

Для других типов размеров стоит учитывать, что:

Размеры

  • если указан any_size или fixed_size, то элемент может выходить за рамки constraints. Например, в примере из раздела «Constraints», если в текстовых view задать длинный текст, то он будет выходить за рамки ConstraintLayout;
  • на размеры ConstraintLayout влияют view с fixed_size, any_size и match_constraint_wrap. Если покажется, что размер ConstraintLayout рассчитан неверно, скорее всего, виновата одна из view с такими размерами;

Задаем размеры View на основе соотношения сторон

ConstraintLayout позволяет рассчитывать высоту или ширину view на основе заданного соотношения сторон. То есть, например, при соотношении сторон 16:9 , если высота будет 900dp , то ширина рассчитается, как 1600dp .

За это отвечает атрибут layout_constraintDimensionRatio . Задать соотношение сторон можно в двух форматах: текстовом 16:9 или числовом 1.8 . При этом перед значением можно указать символ стороны, которая находится в числителе соотношения. Например, H,16:9 будет означать, что 16 — это значение, соотвествующее высоте (H), а 9 — ширине (W).

Значение в layout_constraintDimensionRatio учитывается при расчете размеров view, только если хотя бы одна из сторон выставлена в match_constraint_wrap или match_constraint_spread.

Соотношение сторон

Выставляем относительное расположение View внутри ConstraintLayout

Если у view привязать два горизонтальных constraints, то ей можно выставить горизонтальное относительное расположение. То же применимо и для вертикальных constraints.

За горизонтальное расположение отвечает атрибут layout_constraintHorizontal_bias , за вертикальное — layout_constraintVertical_bias . Указывается относительное расположение значением от 0 до 1.

По сути, это более гибкая замена атрибута layout_gravity . Например, для горизонтального расположения 0 будет означать расположение крайне слева, 0.5по центру, 1крайне справа. По умолчанию — 0.5.

Теперь, например, выставим значение 0.3. Это будет означать, что 30% не заполненного view места будет слева от view, а 70% — справа. Если же размер view больше размера расстояния между constraints, то 30% выходящего за constraints размера будет слева от ограничений, а 70% — справа.

Относительное расположение

Небольшое важное замечание: если в манифесте выставлена поддержка RTL языков, то layout_constraintHorizontal_bias вместо «слева» будет располагать элементы «от начала», а вместо «справа» — «от конца». То есть тем, кто поддерживает RTL языки стоит учитывать, что явно выставить расположение «слева» и «справа» не выйдет. По крайней мере, я такой возможности не нашел.

Особенности привязки линий

После презентации ConstraintLayout его часто сравнивали с RelativeLayout. Но, на самом деле, у них принципиально разные расчеты расположения элементов. В RelativeLayout у view просто указывается, с какой стороны другой view ей нужно находиться — «слева от», «справа от» и т.д. В ConstraintLayout constraints привязываются к сторонам других views и расположение view зависит от того, как ее constraints будут рассчитаны.

Для расположения constraint сперва рассчитывается расположение view, к которой этот constraint привязан. А для расположения view сперва рассчитываются все указанные для нее constraints. Циклические зависимости view и constraints при этом запрещены, так что, фактически, внутри ConstraintLayout строится направленный ациклический граф зависимостей constraints от view и view от constraints. Все расчеты производятся последовательно, начиная от не зависимых элементов графа.

Для простоты советую разделять вертикальные и горизонтальные зависимости на две независимые группы, как-будто внутри ConstraintLayout строится отдельно граф вертикальных зависимостей и отдельно — горизонтальных.

Но, вообще говоря, стоит понимать, что вертикальные параметры view, косвенно, могут зависеть от горизонтальных параметров другой view. Пример — расчет размера на основе соотношения сторон: при изменении ширины view меняется и ее высота. То есть, если у view изменится ширина из-за изменений ее горизонтальных constraints, то высота view тоже изменится.

Теперь рассмотрим любопытный пример расчета вертикальных constraints:

Пример !

Результат:

View A просто привязан к левой и верхней сторонам ConstraintLayout, то есть находится слева сверху.

View B привязан странным образом — Top(B)->bottom(A) и Bottom(B)->top(A) , — расстояние между его вертикальными constraints, фактически, отрицательное. Сама высота B выставлена в match_constraint_spread.

View C находится справа от A — Left(C)toRight(A) — и (вроде как) снизу от B — Top(C)toBottom(B) .

По горизонтальному расположению вопросов возникнуть не должно. Теперь объясню вертикальное расположение.

Последовательность вертикальных расчетов:

  1. Для расчета C необходимо рассчитать ее нижний constraint;
  2. Для расчета нижнего constraint C необходимо рассчитать верхнюю сторону B ;
  3. Для расчета верхней стороны B необходимо рассчитать ее нижний и верхний constraints;
  4. Для расчета нижнего и верхнего constraints B необходимо рассчитать верхнюю и нижнюю стороны A ;
  5. A просто располагается слева-сверху, размеры рассчитывает сам.

Результаты вертикальных расчетов:

  1. Верхняя сторона A находится на уровне верхней стороны ConstraintLayout, нижняя рассчитывается по размеру текста A , так как у A высота wrap_content;
  2. Верхний constraint B на уровне нижней стороны A , нижний привязан к верхней стороне A , то есть он на уровне верхней стороны ConstraintLayout;
  3. Так как высота B — match_constraint_spread, то верхняя сторона B — на уровне нижней стороны A , а нижняя — на уровне верхней стороны ConstraintLayout. Это странно, но, фактически, высота B — отрицательная.
  4. Верхний constraint C привязан к нижней стороне B , то есть он на уровне верхней стороны ConstraintLayout;
  5. В итоге, верхняя сторона C на уровне верхней стороны ConstraintLayout, нижняя рассчитывается по размеру текста C , так как у A высота wrap_content.

В общем, на мой взгляд, такой алгоритм расчета стоит учитывать, чтобы понимать, где будет располагаться view в конечном счете.

Особенности привязки по Baseline

View, привязанная по Baseline, не может быть ограничена сверху и снизу, то есть Top и Bottom constraints будут игнорироваться. Это значит, что для такой view нельзя выставить размер match_constraint_spread или match_constraint_wrap.

Из этого не совсем очевидно следует, что по Baseline стоит привязывать невысокие view к высоким. Иначе есть шанс, что высокая view выйдет за рамки ConstraintLayout или размер ConstraintLayout будет рассчитан неверно.

Пример некорректной Baseline-привязки:

Некорректное отображение Baseline

Результат:

Высота ConstraintLayout (в черной рамке) равна высоте большой TextView (в красной рамке), так как высота TextView выставлена, как wrap_content .

Базовая линия большой TextView привязана к базовой линии малой TextView (в зеленой рамке), так что текст находится на одной линии.

При этом большая TextView выходит за рамки ConstraintLayout.

Создаем цепи (chains)

При привязке сторон есть одно интересное правило — если привязать две стороны двух элементов друг к другу Left(B)toRight(A) и Right(A)toLeft(B) , то элементы будут выделены в цепь и к ним будут применяться особые правила расположения.

Цепью считается набор элементов, стороны которых привязаны друг к другу. Цепи определяются автоматически на основе привязок элементов внутри ConstraintLayout . Цепь располагается на основе привязок ее крайних элементов, а элементы внутри цепи располагаются по правилам определенного стиля цепи. Стиль цепи задается атрибутом layout_constraint_chainStyle , где X — Horizontal для горизонтальных цепей или Vertical для вертикальных.

Пример цепи: правило Right(A)toLeft(B) + Left(B)toRight(A) свяжет элементы A и B в цепь, а Left(A)toLeft(parent) + Right(B)toRight(parent) привяжет всю цепь элементов к внешним сторонам ConstraintLayout .

Стиль цепи и его параметры берутся из атрибутов головного элемента цепи — самого левого, начального или самого верхнего.

Стиль spread

Элементы цепи распределяются равномерно, то есть отступы между элементами и от элементов до границ цепи будут одинаковые. Используется по умолчанию;

Spread

Стиль spread_inside

Элементы цепи распределяются так же, как и при стиле spread , но отступы от границ цепи всегда равны нулю;

Spread inside

Стиль packed

Элементы располагаются группой друг за другом. Такой стиль позволяет устанавливать относительную позицию группы элементов в доступном цепи пространстве через атрибут layout_constraint_bias . Bias атрибут нужно указывать у головного элемента цепи;

Packed
Packed bias

Стиль weighted

Элементы располагаются в соответствии с их весом по аналогии с тем, как работает LinearLayout. Чтобы такой стиль заработал, одна из view цепи должна иметь размер match_constraint_spread. Для указания веса элемента используются атрибуты layout_constraintHorizontal_weight и layout_constraintVertical_weight .

Weighted

Указываем отступы с учетом скрытых элементов

Отступы указываются стандартными атрибутами layout_margin , где X — сторона отступа ( Left/Right/Top/Bottom/Start/End ). Эти отступы применяются к линиям привязки элемента, а не к сторонам элемента. Это значит, что линия привязки будет с отступом от стороны, к которой она привязана.

Отдельные правила были введены для скрытых элементов, то есть элементов, у которых Visibility выставлено в значение GONE . Когда элемент скрыт, обычные отступы от его сторон игнорируются, но используются специальные gone-отступы . Его размеры при этом при расчетах считаются равными нулю. Gone-отступы представлены атрибутами: layout_goneMargin , где X — сторона отступа.

Другими словами, допустим, у элемента A выставлен отступ слева от элемента B равный 10dp , а gone-отступ слева выставлен 50dp . Если элемент B скрыт ( GONE ), то отступ элемента A слева будет 50dp , если элемент B — VISIBLE или INVISIBLE , то отступ элемента A слева будет 10dp .

Отступы и учет скрытых элементов

Разбираемся с Guidelines

Guideline — это аналог линии, устанавливаемой на макетах в визуальных редакторах, по которой дизайнеры выравнивают элементы. Такую линию представляет view класса android.support.constraint.Guideline . Guideline может быть горизонтальным или вертикальным — это указывается атрибутом android:orientation . Сам guideline нулевого размера, не занимает места в контейнере и всегда привязан только к сторонам ConstraintLayout.

Guideline используется, чтобы привязывать к нему стороны view выравнивая их тем самым по одной линии.

Расположение guideline контролируется тремя атрибутами:

  • layout_constraintGuide_begin — отступ от левой ( Left , а не Start ) стороны ConstraintLayout для вертикальных gudeline и от верхней стороны — для горизонтальных;
  • layout_constraintGuide_end — отступ от правой ( Right , а не End ) стороны ConstraintLayout для вертикальных guideline и от нижней стороны — для горизонтальных;
  • layout_constraintGuide_percent — относительный отступ guideline в процентах от левой стороны ConstraintLayout для вертикальных gudeline и от верхней стороны — для горизонтальных. Указывается числом от 0 до 1.

Guidelines

Настраиваем параметры ConstraintLayout из кода

За это отвечает отдельный класс — android.support.constraint.ConstraintSet .

Создать этот ConstraintSet можно тремя способами:

    скопировать параметры из существующего ConstraintLayout ;

 ConstraintLayout constraintLayout = LayoutInflater.from(context).inflate(R.layout.my_constraint_layout, null); ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(constraintLayout);
 ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(context, R.layout.my_constraint_layout);
 ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(constraintSet2);

Описание методов для изменения ConstraintSet можно посмотреть в документации (их очень много).

Чтобы применить ConstraintSet к ConstraintLayout используйте метод applyTo .

 final ConstraintLayout constraintLayout = (ConstraintLayout) findViewById(R.id.constr); ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(constraintLayout); constraintSet.setHorizontalBias(R.id.sample_view, 0.5f); constraintSet.applyTo(constraintLayout);

Естественно, также можно напрямую изменять LayoutParams конкретного элемента и затем явно вызывать requestLayout . Класс, представляющий все параметры расположения элемента в ConstraintLayout — это android.support.constraint.ConstraintLayout.LayoutParams .

Коротко об анимации

Для анимаций в ConstraintLayout не предусмотрено специальных методов. ConstraintLayout наследуется от ViewGroup , так что для анимации расположения элементов используются те же инструменты, что и для обычных контейнеров: ValueAnimator , TransitionManager и так далее.

Стоит ли пользоваться ConstraintLayout?

  • обладает широким спектром возможностей, что позволяет реализовывать сложные взаиморасположения элементов на экране;
  • в Android Studio для этого контейнера имеется удобный визуальный редактор;
  • в большинстве случаев можно избежать излишней вложенности контейнеров друг в друга, что положительно влияет на производительность и читаемость кода;
  • заменяет стандартные контейнеры, то есть достаточно выучить один раз ConstraintLayout и не пользоваться в дальнейшем такими контейнерами, как: FrameLayout , LinearLayout , RelativeLayout , GridLayout , PercentRelativeLayout ;
  • содержится в Support Library, так что баги будут правиться и, возможно, будут появляться новые функции.
  • в силу большого количества возможностей уходит больше времени на понимание, как он работает;
  • при большом количестве элементов и взаимосвязей между ними сложно проводить код-ревью разметки — иерархическую вложенность контейнеров воспринимать проще, чем большое количество взаимосвязанных элементов в одном контейнере;
  • в определенных случаях по производительности может быть хуже иерархии стандартных контейнеров, так как дополнительно требуется время на построение дерева зависимостей;
  • несмотря на релизную версию, все еще можно наткнуться на неприятные баги;
  • атрибутов у views будет гораздо больше, чем при использовании обычных layouts.

В компании Touch Instinct мы собираемся, как минимум, попробовать этот компонент. Особенно интересно посмотреть, как он поведет себя при использовании в ячейках RecyclerView — там часто изменяются значения элементов, что обычно приводит к пересчету расположения элементов в ячейке и иногда пересчету ее размера.

Разработчики Google проделали большую работу и у ConstraintLayout есть все шансы стать стандартным при разработке элементом UI, какими стали те же RecyclerView и CoordinatorLayout.

  • Блог компании Touch Instinct
  • Разработка мобильных приложений
  • Разработка под Android

ConstraintLayout

Новый макет ConstraintLayout появился в Android Studio 2.2 и доступен для устройств с версии Android 2.3. Его можно найти в разделе Layouts. Гугл очень расхваливает данный макет и советует всем переходить на него и даже создал специальный конвертер для этой задачи. Если у вас имеется старый проект, то достаточно щёлкнуть правой кнопкой мыши на корневом элемента макета и выбрать пункт Convert layout to ConstraintLayout. В диалоговом окне ничего не трогаем.

ConstraintLayout

В build.gradle модуля прописывается ссылка на библиотеку и проект начинает синхронизироваться. Сейчас уже активно развивается ветка 2.х.x, лучше сразу переходить на неё.

 // ветка 1.х.х implementation 'androidx.constraintlayout:constraintlayout:1.1.3' // ветка 2.х.x implementation 'androidx.constraintlayout:constraintlayout:2.0.4' // стабильная версия implementation 'androidx.constraintlayout:constraintlayout:2.1.0-alpha2' // альфа-версия 

В Android Studio 2.3 и выше в шаблонах по умолчанию теперь используется ConstraintLayout.

Если в режиме дизайна выбран данный компонент, то на панели инструментов вам доступны несколько кнопок.

  • View Options с пунктами Show Constraints — выводит заданные ограничения в режимах предварительного просмотра и раскладки. В одних случаях этот режим просмотра полезен, в других нет. При большом количестве ограничений эта кнопка выводит слишком много информации, Show Margins, Fade Unselected views.
  • Turn On Autoconnect — при включении режима Autoconnect ограничения будут автоматически настраиваться при перетаскивании представлений в область предварительного просмотра. Студия старается угадать, какие ограничения должен иметь компонент, и создавать их по мере необходимости
  • Default Margins — стандартное значение для отступов. Можете устанавливать отдельно для каждого компонента. Выбрали компонент, установили значение, затем снова выбрали другой компонент и установили другое значение
  • Clear All Constraints — удаляет все ограничения из макета
  • Infer Constraints — автоматически создаёт ограничения. Срабатывает только при нажатии кнопки. Функциональность Autoconnect срабатывает каждый раз, когда в файл макета добавляется новый компонент
  • GuideLines с двумя опциями: Add Vertical GuideLine и Add Horizontal GuideLine. Смотри ниже

Очень часто на форумах встречается вопрос, почему в режиме дизайна макет выглядит хорошо, а при запуске приложения все компоненты сбиваются в верхний левый угол. Для решения этой проблемы попробуйте нажать на кнопку Infer Constraints, которая создаст дополнительные ограничения.

ConstraintLayout является наследником ViewGroup и местами похож на RelativeLayout, но более продвинут. Код разметки в XML-представлении:

Управление компонентами внутри данного контейнера достаточно сильно отличается от старого взаимодействия. Придётся всем переучиваться.

При его использовании нет смысла использовать XML-представление, только в режиме Design, когда вы можете подвигать все компоненты в визуальном редакторе.

Рассмотрим отдельные элементы, которые используются для редактирования макета. Переключитесь в режим Blueprint, чтобы ничего нас не отвлекало от работы.

Blueprint

Так выглядит выбранный компонент.

ConstraintLayout

Квадратные опорные точки в углах компонента позволяют изменять его размеры. Круглые опорные точки по краям позволяют управлять отступами от краёв экрана и других компонентов.

Продолговатый закругленный прямоугольник указывает на базовую линию текста. Пригодится при выравнивании по базовой линии другого компонента.

Также можно увидеть зигзагообразные направляющие-пружинки, которые нужны для особых случаев. Если провести мышкой над опорной точкой пружинки, она красиво активируется. Последующий щелчок удаляет пружинку. Если компонент удерживался двумя пружинками сверху и снизу, то удаление одной из них притянет компонент к верхней или нижней части экрана. Лучше самостоятельно проверить у себя, так как словами трудно описать.

Пружинки также можно легко восстановить, если щёлкнуть по круглой опорной точке и потянуть её к краю экрана.

Наверху на панели инструментов можно активировать автоматический режим размещения компонентов Autoconnect в виде магнита . В этом случае вы просто бросаете на экран нужный компонент, а далее студия сама пытается найти нужные параметры для него. Всё это происходит с красивой анимацией. Естественно, вы можете всё переиграть и поставить в нужную позицию.

На той же панели есть инструмент Infern Constraints в виде пары звёздочек . Действует как Autoconnect, только работает не с одним компонентом, редактируемым в данный момент, а со всем макетом сразу, используя математические расчёты, чтобы определить, какие компоненты нужно привязать к другим, исходя из их местоположения на экране.

Теперь рассмотрим настройки в панели Properties.

ConstraintLayout

Набор из трёх стрелок внутри квадрата означают атрибут wrap_content. Если вы измените этот атрибут с помощью выпадающего списка или вручную напишите размер в dp, то увидите, что стрелки заменятся на прямые (фиксированный размер ) или пружинку (match_constraints , который является приблизительным аналогом атрибута match_parent и имеет значение 0dp).

По бокам квадрата имеются числа. Если подвести к ним мышку, то появится выпадающий список с определёнными значениями: 0, 8, 16, 24, 32. Они отвечают за атрибут margin (отступы).

ConstraintLayout

По бокам можно увидеть также ползунки с числами в кружочке. В примере виден только горизонтальный ползунок, при других настройках увидите и вертикальный. С их помощью можно центрировать компонент относительно экрана и других объектов.

Aspect ration (Соотношение сторон)

Если у компонента есть двусторонняя вертикальная привязка и значение высоты установлено в match_constraints (0dp), то можно настроить так, чтобы высота зависела от ширины. В углу появится треугольник, щёлкнув на котором можно затем установить желаемое соотношение. Потом можно изменять ширину, чтобы увидеть, как высота подстраивается под ширину.

Аналогично можно настроить зависимость ширины от высоты, предварительно сначала установив двустороннюю горизонтальную привязку.

ConstraintLayout Aspect Ratio

GuideLine

На панели инструментов также имеется значок GuideLines с двумя опциями: Add Vertical GuideLine и Add Horizontal GuideLine. Если ими воспользоваться, то в XML-файле появятся такие строчки:

По сути, это View, размер которого 0, что соответствует View.GONE. На этапе разработки мы видим только полоски, а во время работы приложения ничего не видим. Данные элементы помогают разместить компоненты аккуратно относительно линии.

Направляющие пригодятся, если одни и те же значения отступов повторяются для нескольких компонентов. Направляющие можно указывать в dp от края экрана или задать в процентах от ширины экрана. Чтобы переключаться между разными режимами, вы можете нажать на круглый значок Guideline.

Не всегда с помощью визуального редактора можно добиться нужного результата, тогда нужно переключиться в XML-режим. Один из таких случаев описан в статье Square Island: Constraint Layout: Icon Label Text.

Если есть желание работать через XML, то следует запомнить очень много атрибутов, например, для выравнивания относительно друг друга:

  • app:layout_constraintStart_toStartOf=»@id/view»
  • app:layout_constraintLeft_toLeftOf=»@id/view»
  • app:layout_constraintEnd_toEndOf=»@id/view»
  • app:layout_constraintRight_toRightOf=»@id/view»
  • app:layout_constraintTop_toTopOf=»@id/view»
  • app:layout_constraintBaseline_toBaselineOf=»@id/view»
  • app:layout_constraintBottom_toBottomOf=»@id/view»
  • app:layout_constraintStart_toEndOf=»@id/view»
  • app:layout_constraintLeft_toRightOf=»@id/view»
  • app:layout_constraintEnd_toStartOf=»@id/view»
  • app:layout_constraintRight_toLeftOf=»@id/view»
  • app:layout_constraintTop_toBottomOf=»@id/view»
  • app:layout_constraintBottom_toTopOf=»@id/view»

Атрибут app:layout_constraintHorizontal_bias используется float-значения от 0 до 1, для выравнивания по оси.

Атрибут app:layout_constraintDimensionRatio=»4:3″ указывает, что нужно использовать данные пропорции по высоте и ширине для данного компонента. Также встречается модификация атрибута app:layout_constraintDimensionRatio=»H, 1:1″.

Chains — Скованные одной цепью

Несколько элементов можно сковать одной цепью. Допустим, у нас есть три кнопки. Выделяем их и через контекстное меню выбираем Center Horizontally. Снизу у выбранных компонентов появится символ цепи, а между ними будут нарисована связь в виде цепей. Если последовательно щёлкать по значку цепи, то увидите, как кнопки будут центрироваться с разными стилями:

  • spread — Свободное пространство равномерно распределяется между выбранными компонентами и краями родителя (например, экрана)
  • spread_inside — Крайние компоненты прижимаются к границам родителя, свободное пространство равномерно распределяется только между остальными компонентами
  • packed — Свободное пространство равномерно распределяется между крайними компонентами и границами родителя. Вы можете использовать margin для отступов

За цепи отвечают стили.

  • app:layout_constraintHorizontal_chainStyle=»spread»
  • app:layout_constraintVertical_chainStyle=»spread»
  • app:layout_constraintHorizontal_chainStyle=»spread_inside»
  • app:layout_constraintVertical_chainStyle=»spread_inside»
  • app:layout_constraintHorizontal_chainStyle=»packed»
  • app:layout_constraintVertical_chainStyle=»packed»

Также можно присвоить кнопкам вес, когда одна кнопка может быть больше остальных, это поведение знакомо по LinearLayout. Для этих целей используются атрибуты

  • layout_constraintHorizontal_weight
  • layout_constraintVertical_weight

Как и в LinearLayout, чтобы использовать вес, надо поставить размер компонента в 0dp.

На рисунке этот вариант представлен в третьем примере.

ConstraintLayout

Проценты

Можно указывать значения ширины и высоты в процентах через атрибуты layout_constraintWidth_percent, layout_constraintHeight_percent. Все View-компоненты поддерживают данные атрибуты. Они позволяют ограничить компонент процентным значением в рамках всего доступного пространства. Например, мы хотим видеть кнопку, которая будет занимать 70% в рамках свободного для неё места.

Barriers

Barriers — это виртуальный View, который используется как шаблон. Он применяется для нескольких компонентов неизвестного размера – если один из них увеличивается, то барьер подстроит размер остальных под наибольшую высоту или ширину. Барьеры могут быть вертикальными и горизонтальными и создаваться сверху, снизу, слева или справа от нужных элементов. Другие элементы будут подстраиваться.

 app:barrierDirection="right" app:constraint_referenced_ids="button,text_view" 

Барьеры полезны, когда вы создаёте локализованные строки или отображаете контент, созданный пользователем, размер которого вы не можете предсказать.

Groups

Groups — теперь можно логически группировать определённые виды. По сути, это некий контейнер, который содержит ссылки на ID компонентов, а не сами компоненты. При помощи группы вы можете установить видимость всех компонентов в контейнере. Это может пригодиться, когда сразу несколько элементов должны изменять свою видимость или другие свойства.

Анимация

Для анимации разметки ConstraintLayout используется ConstraintSet.

Circular

С помощью Circular мы можем настроить два компонента так, чтобы одно находилось на определённом расстоянии и под определённым углом от другого.

  • layout_constraintCircle — указываем идентификатор компонента, который будет центром окружности
  • layout_constraintCircleRadius — расстояние от центра окружности до компонента
  • layout_constraintCircleAngle — угол (в градусах, от 0 до 360)

При создании круговых зависимостей углы начинаются сверху и изменяются по часовой стрелке. Например, таким образом нужно описать среднюю кнопку в примере выше:

Такой способ пригодится для анимации аналоговых часов или похожих примеров.

Placeholder

Элемент Placeholder позволяет использовать место на экране в качестве временного заполнителя. Используя анимацию, можно динамически перемещать компонент на место заполнителя.

ConstraintLayoutStates (2.0.0)

В ветке 2.х появилась новая функциональность — переключение между макетами экрана. Это удобно, когда требуются небольшие изменения на одном экране. Вы должны создать несколько макет с одинаковыми компонентами, но при этом можете изменить их свойства (видимость, местоположение и т.д). В нужный момент программно переключаетесь на нужный макет.

Создадим три разных макета в папке layout.

res/layout/activity_cl_states_start.xml

res/layout/activity_cl_states_loading.xml

res/layout/activity_cl_states_end.xml

В нашем примере во всех макетах имеются ProgressBar и Button с разной видимостью и позицией.

Следующий шаг — создать в папке res/xml новый файл, описывающий три созданных макета.

res/xml/constraint_layout_states.xml

Осталось написать программную часть. Сначала мы загружаем первый макет в стандартном методе setContentView(). Затем загружаем описания созданных нами макетов через loadLayoutDescription() объекта-контейнера нашего ConstraintLayout. Теперь мы можем переключаться между макетами через constraintLayout.setState().

В примере при нажатии кнопки мы имитируем долгоиграющую задачу и выводим ProgressBar, когда задача будет завершена, то выводим третий макет, где кнопка находится уже в другом месте.

 package ru.alexanderklimov.hellokot import android.os.Bundle import android.os.Handler import androidx.appcompat.app.AppCompatActivity import androidx.core.os.HandlerCompat.postDelayed import kotlinx.android.synthetic.main.activity_cl_states_end.* class MainActivity : AppCompatActivity() < private val handler = Handler() override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_cl_states_start) stateConstraintLayout.loadLayoutDescription(R.xml.constraint_layout_states) var changed = false button.setOnClickListener < stateConstraintLayout.setState(R.id.loading, 0, 0) postDelayed(handler, < stateConstraintLayout.setState(if (changed) R.id.start else R.id.end,0, 0) changed = !changed >, null, 3000L) > > > 

Flow

В версии 2.0.0-alpha5 появился новый вид виртуального макета Flow, который позволяет работать с объектами разных размеров.

Layer

Новый виртуальный помощник Layer работает с группой компонентов, применяя к ним различные трансформации: вращение, перемещение, масштабирование.

Другие атрибуты

Стоит обратить внимание на следующие атрибуты. Они часто используются, если в контейнере содержится компонент RecyclerView.

  • android:background=»?android:attr/selectableItemBackground»
  • android:clickable=»true»
  • android:focusable=»true»

Примеры

Стандартный случай: картинка и две текстовые метки. Верхней край картинки должен совпадать с верхней частью первой строки, а нижний край картинки с нижней частью второй строки. При этом картинка векторная и должна сохранять пропорции. Макет при изменении размера шрифта должен сохранять свою структуру.

Обновления

Библиотека постоянно развивается. Некоторые пункты меню были переработаны и изменены. Следите за изменениями.

LinearLayout

В студии макет LinearLayout представлен двумя вариантами — Horizontal и Vertical. Макет LinearLayout выравнивает все дочерние объекты в одном направлении — вертикально или горизонтально. Направление задается при помощи атрибута ориентации android:orientation:

  • android:orientation=»horizontal»
  • android:orientation=»vertical»

Все дочерние элементы помещаются в стек один за другим, так что вертикальный список компонентов будет иметь только один дочерний элемент в ряду независимо от того, насколько широким он является. Горизонтальное расположение списка будет размещать элементы в одну строку с высотой, равной высоте самого высокого дочернего элемента списка.

В этом примере используются два способа размещения элементов TextView: по горизонтали и по вертикали.

LinearLayout

У разметки LinearLayout есть интересный атрибут android:layout_weight, который назначает индивидуальный вес для дочернего элемента. Этот атрибут определяет «важность» представления и позволяет этому элементу расширяться, чтобы заполнить любое оставшееся пространство в родительском представлении. Заданный по умолчанию вес является нулевым.

Например, если есть три текстовых поля, и двум из них объявлен вес со значением 1, в то время как другому не даётся никакого веса (0), третье текстовое поле без веса не будет расширяться и займёт область, определяемую размером текста, отображаемого этим полем. Другие два расширятся одинаково, чтобы заполнить остаток пространства, не занятого третьим полем. Если третьему полю присвоить вес 2 (вместо 0), это поле будет объявлено как «более важное», чем два других, так что третье поле получит 50% общего пространства, в то время как первые два получат по 25% общего пространства.

Также можно указать атрибут android:weightSum. Если атрибуту присвоить значение 100, то можно указывать вес дочерних элементов в удобном виде, как в процентах. Такой способ широко используется веб-мастерами при вёрстке.

Создадим простейшую разметку таким образом, чтобы дочерний элемент занимал половину родительского контейнера:

Weight

Примеры

Рассмотрим возможности LinearLayout на примерах.

Создадим 7 текстовых меток и присвоим им цвета радуги. Расположим их друг за другом. Получим следующий результат

Отцентрируем текст в TextView при помощи свойства Gravity, установив значение Center. Аналогично поступим и с LinearLayout, чтобы выровнять по центру текстовые метки.

Цветные полоски получились слишком узкими. Расширим их за счет увеличения размера шрифта (TextSize) у текстовых меток.

Стало чуть лучше, но все равно пропадает много свободного пространства. Совсем не хочется видеть чёрный цвет сверху и снизу. Здесь нам придёт на помощь свойство Layout weight. Так как число 7 не совсем удобное для деления, то поступим следующим образом. Пяти элементам присвоим вес 0.14, а двум — 0.15, что в сумме даст 1.0. Теперь элементы равномерно заполнят весь экран.

Если мы хотим сделать плавное увеличение высоты полоски, то вес нужно распределить таким образом: 0.08, 0.10, 0.12, 0.14, 0.16, 0.18, 0.22.

Чем хорош этот способ? Мы не привязываемся к точным размерам, а позволяем системе самой расчитывать равномерное распределение элементов по экрану. Если в Eclipse вы выберите режим Landscape, то экран будет выводиться в альбомном режиме и при этом элементы по-прежнему будет равномерно распределены.

Градиентный фон

Если вам нужен градиентный фон для LinearLayout, то создайте в папке res/drawable xml-файл, например, gradient.xml:

Далее остаётся только прописать файл в свойстве Background:

LinearLayout с градиентом

Меняем фон программно

Чтобы программно сменить фоновый цвет у LinearLayout, нужно вызвать метод setBackgroundColor(). Пример изменения фона можно найти в статье Android::Класс android.graphics.Color.

Программная анимация компоновки

Хочу показать один пример программной анимации. Не знаю, имеет ли пример практическую ценность, но для общего развития не помешает. Добавьте в шаблон LinearLayout несколько кнопок, текстовых полей и других элементов на ваше усмотрение. Далее пишем код для обработчика щелчка кнопки и вспомогательный класс для анимации:

 public void onClick(View view) < LinearLayout linearLayout = (LinearLayout) findViewById(R.id.linearLayout); linearLayout.startAnimation(new ViewAnimation()); >public class ViewAnimation extends Animation < int centerX, centerY; @Override public void initialize(int width, int height, int parentWidth, int parentHeight) < super.initialize(width, height, parentWidth, parentHeight); setDuration(5000); setFillAfter(true); setInterpolator(new LinearInterpolator()); centerX = width / 2; centerY = height / 2; >@Override protected void applyTransformation(float interpolatedTime, Transformation transformation) < final Matrix matrix = transformation.getMatrix(); matrix.setScale(interpolatedTime, interpolatedTime); >> 

Когда вы щелкните на кнопке, то LinearLayout будет плавно увеличиваться в размерах. Данный приём можно использовать не только к компоновке, но и к любому объекту View.

Отключаем выравнивание по базовой линии

Допустим, у вас есть следующая разметка:

Если посмотрим, что получилось, то увидим, что средняя кнопка опустилась вниз.

baselineAligned

Строго говоря, разметка составлена не совсем правильно, используя жёстко установленные величины. Но будем считать, что такой код достался от другого программиста и заказчик не хочет его кардинально менять. Причина такого поведения кнопки в том, что по умолчанию Android пытается выравнивать элементы по некой базовой линии. А средняя кнопка имеет слишком длинный текст и она вот таким причудливым образом сместилась вниз. Можно попробовать использовать свойства gravity, но можно поступить проще. Добавьте атрибут android:baselineAligned=»false» к LinearLayout и все три кнопки будут аккуратно находиться на одной линии. Имейте в виду, может пригодится.

Разделители

Начиная с API 11, у LinearLayout появился новый атрибут android:divider, позволяющий задать графический разделитель между кнопками. Также нужно явно включить использование разделителей через атрибут android:showDividers, в котором можно указать, каким образом использовать разделители — только в середине, в начале, в конце — можно комбинировать эти значения.

 android:showDividers="beginning|middle|end" 

Создадим в папке res/drawable файл separator.xml:

Разметка для активности:

Разделители могут оказаться полезными. В статье Grid Spacing on Android показан хороший пример на эту тему.

Допустим, мы хотим вывести в ряд три кнопки под каким-то компонентом, например, логотипом компании. Разметка может быть следующей.

Вместо @dimen/spacing_medium можете подставить 8dp, а цвета придумать свои, если будете проверять пример самостоятельно.

Видно, что напрашивается дизайн в виде сетки. Отсутствие пространства между кнопками может создать неудобства у пользователя. Добавим их. У контейнера @id/buttons_container добавим android:layout_marginTop=»@dimen/spacing_medium», а у первой и второй кнопки добавим android:layout_marginRight=»@dimen/spacing_medium» (напомню, можно использовать 8dp)

Всё отлично работает до того момента, если нам понадобится программно убрать с экрана третью кнопку. Сделать это можно через View.GONE. И что мы увидим?

Теперь вторая кнопка не выровнена по правому краю. Некрасиво. Очень плохим решением станет программный пересчёт всех величин, чтобы выровнять компоненты. Как вариант, использовать другой тип разметки, например, GridLayout. Но у него есть свои проблемы с отступами и вам будет тяжело добиться нужного результата.

Проблема красиво решается с помощью упомянутых выше разделителей. Создадим в папке res/drawable файл spacer_medium.xml:

Теперь кнопки всегда будут аккуратно выравнены по краям, независимо от их количества — две или три.

Программное создание разметки LinearLayout

В некоторых случаях может понадобиться создать LinearLayout программным способом. Сначала создаётся объект LayoutParams, на его основе создаётся LinearLayout, а позже в него добавляются дочерние компоненты.

 LayoutParams params = newLinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); // create a layout LinearLayout layout = newLinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); // create a textview TextView textView = newTextView(this); textView.setText("This is a TextView"); textView.setLayoutParams(params); // create a button Button button = newButton(this); button.setText("This is a Button"); button.setLayoutParams(params); // adds the textview layout.addView(textView); // adds the button layout.addView(button); // create a layout param for the layout LinearLayout.LayoutParams layoutParam = newLinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); this.addContentView(layout, layoutParam); 

Также программно можно управлять настройками LinearLayout через тот же объект LayoutParams. Разместите кнопку с обработчиком щелчка.

 public void onClick(View view)

Каждый щелчок на кнопке будет увеличивать отступ на пять единиц и кнопка будет смещаться в сторону. Интересно, что если убрать TextView, то кнопка перестаёт двигаться. Причина мне неизвестна.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *