Привет! Меня зовут Гавриил, я Android-лид Touch Instinct. В марте Google выкатил релизное обновление ConstraintLayout. Презентовали его еще на прошлом Google I/O. С того момента прошел почти год, ConstraintLayout стал лучше, быстрее и оброс новыми возможностями. Например, приятно, что появилась возможность объединения элементов в цепи — это позволяет использовать ConstraintLayout вместо LinearLayout. О всех новых и старых возможностях ConstraintLayout я и постараюсь рассказать в этой статье. Предупрежу сразу — статья будет длинная и других семи частей не будет. Про визуальный редактор ConstraintLayout в статье не будет ни слова — только XML-разметка (по старинке) и немного кода. Теперь можно использовать ConstraintLayout у себя в проекте: Constraints — это линии, на основе которых располагается view внутри ConstraintLayout. Constraints могут быть привязаны к сторонам самого ConstraintLayout или к сторонам других view внутри ConstraintLayout. Constraints можно разделить на вертикальные и горизонтальные. Горизонтальные constraints: Вертикальные constraints: Вертикальное и горизонтальное constraints друг с другом не связаны. Напомню, что Baseline — это линия выравнивания контента элемента. Пример — для TextView это линия строки, на которой пишется текст. Если у view выставлен Baseline сonstraint, то базовая линия элемента будет находиться на уровне базовой линии view, к которой привязан сonstraint. Для начала, проще всего рассматривать сonstraints, как стороны view. То есть можно, например, привязать левую сторону view B к правой стороне view A — тогда view B будет располагаться справа от view A. Общий формат атрибутов для привязки сonstraint выглядит следующим образом: Где: Пример привязок:  Основные правила привязки сторон: Чтобы задать размеры view, используются обязательные атрибуты layout_width и layout_height, и необязательные атрибуты layout_constraintWidth_default и layout_constraintHeight_default. Значение атрибутов layout_constraintWidth_default и layout_constraintHeight_default по умолчанию равно spread. Размер view могут быть указан, как (на примере высоты): Важно! Указывать размер match_parent или fill_parent запрещено. Чтобы размер view совпадал с размерами ConstraintLayout, достаточно просто привязать constraints к сторонам ConstraintLayot и использовать размер match_constraint_spread. Если указан размер match_constraint_wrap или match_constraint_spread, стоит учесть, что: Для других типов размеров стоит учитывать, что: 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 привязать два горизонтальных 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). По горизонтальному расположению вопросов возникнуть не должно. Теперь объясню вертикальное расположение. Последовательность вертикальных расчетов: Результаты вертикальных расчетов: В общем, на мой взгляд, такой алгоритм расчета стоит учитывать, чтобы понимать, где будет располагаться view в конечном счете. View, привязанная по Baseline, не может быть ограничена сверху и снизу, то есть Top и Bottom constraints будут игнорироваться. Это значит, что для такой view нельзя выставить размер match_constraint_spread или match_constraint_wrap. Из этого не совсем очевидно следует, что по Baseline стоит привязывать невысокие view к высоким. Иначе есть шанс, что высокая view выйдет за рамки ConstraintLayout или размер ConstraintLayout будет рассчитан неверно. Пример некорректной Baseline-привязки: Результат:
 Высота ConstraintLayout (в черной рамке) равна высоте большой TextView (в красной рамке), так как высота TextView выставлена, как wrap_content. Базовая линия большой TextView привязана к базовой линии малой TextView (в зеленой рамке), так что текст находится на одной линии. При этом большая TextView выходит за рамки ConstraintLayout. При привязке сторон есть одно интересное правило — если привязать две стороны двух элементов друг к другу Left(B)toRight(A) и Right(A)toLeft(B), то элементы будут выделены в цепь и к ним будут применяться особые правила расположения. Цепью считается набор элементов, стороны которых привязаны друг к другу. Цепи определяются автоматически на основе привязок элементов внутри ConstraintLayout. Цепь располагается на основе привязок ее крайних элементов, а элементы внутри цепи располагаются по правилам определенного стиля цепи. Стиль цепи задается атрибутом layout_constraint{X}_chainStyle, где X — Horizontal для горизонтальных цепей или Vertical для вертикальных. Пример цепи: правило Right(A)toLeft(B) + Left(B)toRight(A) свяжет элементы A и B в цепь, а Left(A)toLeft(parent) + Right(B)toRight(parent) привяжет всю цепь элементов к внешним сторонам ConstraintLayout. Стиль цепи и его параметры берутся из атрибутов головного элемента цепи — самого левого, начального или самого верхнего. Элементы цепи распределяются равномерно, то есть отступы между элементами и от элементов до границ цепи будут одинаковые. Используется по умолчанию;  Элементы цепи распределяются так же, как и при стиле spread, но отступы от границ цепи всегда равны нулю;  Элементы располагаются группой друг за другом. Такой стиль позволяет устанавливать относительную позицию группы элементов в доступном цепи пространстве через атрибут layout_constraint{*}_bias. Bias атрибут нужно указывать у головного элемента цепи; 
 Элементы располагаются в соответствии с их весом по аналогии с тем, как работает LinearLayout. Чтобы такой стиль заработал, одна из view цепи должна иметь размер match_constraint_spread. Для указания веса элемента используются атрибуты layout_constraintHorizontal_weight и layout_constraintVertical_weight.  Отступы указываются стандартными атрибутами layout_margin{X}, где X — сторона отступа (Left/Right/Top/Bottom/Start/End). Эти отступы применяются к линиям привязки элемента, а не к сторонам элемента. Это значит, что линия привязки будет с отступом от стороны, к которой она привязана. Отдельные правила были введены для скрытых элементов, то есть элементов, у которых Visibility выставлено в значение GONE. Когда элемент скрыт, обычные отступы от его сторон игнорируются, но используются специальные gone-отступы. Его размеры при этом при расчетах считаются равными нулю. Gone-отступы представлены атрибутами: layout_goneMargin{X}, где X — сторона отступа. Другими словами, допустим, у элемента A выставлен отступ слева от элемента B равный 10dp, а gone-отступ слева выставлен 50dp. Если элемент B скрыт (GONE), то отступ элемента A слева будет 50dp, если элемент B — VISIBLE или INVISIBLE, то отступ элемента A слева будет 10dp. Пример:  Guideline — это аналог линии, устанавливаемой на макетах в визуальных редакторах, по которой дизайнеры выравнивают элементы. Такую линию представляет view класса android.support.constraint.Guideline. Guideline может быть горизонтальным или вертикальным — это указывается атрибутом android:orientation. Сам guideline нулевого размера, не занимает места в контейнере и всегда привязан только к сторонам ConstraintLayout. Guideline используется, чтобы привязывать к нему стороны view выравнивая их тем самым по одной линии. Расположение guideline контролируется тремя атрибутами:  За это отвечает отдельный класс — android.support.constraint.ConstraintSet. Создать этот ConstraintSet можно тремя способами: Описание методов для изменения ConstraintSet можно посмотреть в документации (их очень много). Чтобы применить ConstraintSet к ConstraintLayout используйте метод applyTo. Естественно, также можно напрямую изменять LayoutParams конкретного элемента и затем явно вызывать requestLayout. Класс, представляющий все параметры расположения элемента в ConstraintLayout — это android.support.constraint.ConstraintLayout.LayoutParams.  Для анимаций в ConstraintLayout не предусмотрено специальных методов. ConstraintLayout наследуется от ViewGroup, так что для анимации расположения элементов используются те же инструменты, что и для обычных контейнеров: ValueAnimator, TransitionManager и так далее. Основные преимущества: Основные недостатки: В компании Touch Instinct мы собираемся, как минимум, попробовать этот компонент. Особенно интересно посмотреть, как он поведет себя при использовании в ячейках RecyclerView — там часто изменяются значения элементов, что обычно приводит к пересчету расположения элементов в ячейке и иногда пересчету ее размера. Разработчики Google проделали большую работу и у ConstraintLayout есть все шансы стать стандартным при разработке элементом UI, какими стали те же RecyclerView и CoordinatorLayout.