
В версии ConstraintLayout 2.1 появилось несколько функций для управления складными устройствами, в том числе SharedValues, ReactiveGuide и улучшенная поддержка анимации с помощью MotionLayout.
Общие ценности
Мы добавили новый механизм для внедрения значений времени выполнения в ConstraintLayout. Он предназначен для значений на уровне системы, поскольку все экземпляры ConstraintLayout могут получить доступ к значению.
В случае со складными устройствами мы можем использовать этот механизм, чтобы внедрить положение сгиба во время выполнения:
Kotlin
ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold)
Java
ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold);
В специальном помощнике вы можете получить доступ к общим значениям, добавив прослушиватель для любых изменений:
Kotlin
val sharedValues: SharedValues = ConstraintLayout.getSharedValues() sharedValues.addListener(mAttributeId, this)
Java
SharedValues sharedValues = ConstraintLayout.getSharedValues(); sharedValues.addListener(mAttributeId, this);
В примере FoldableExperiments показано, как мы определяем положение сгиба с помощью библиотеки Jetpack WindowManager и передаем его в ConstraintLayout.
Kotlin
inner class StateContainer : Consumer<WindowLayoutInfo> { override fun accept(newLayoutInfo: WindowLayoutInfo) { // Add views that represent display features for (displayFeature in newLayoutInfo.displayFeatures) { val foldFeature = displayFeature as? FoldingFeature if (foldFeature != null) { if (foldFeature.isSeparating && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL ) { // The foldable device is in tabletop mode val fold = foldPosition(motionLayout, foldFeature) ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold) } else { ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, 0); } } } } }
Java
class StateContainer implements Consumer<WindowLayoutInfo> { @Override public void accept(WindowLayoutInfo newLayoutInfo) { // Add views that represent display features for (DisplayFeature displayFeature : newLayoutInfo.getDisplayFeatures()) { if (displayFeature instanceof FoldingFeature) { FoldingFeature foldFeature = (FoldingFeature)displayFeature; if (foldFeature.isSeparating() && foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL ) { // The foldable device is in tabletop mode int fold = foldPosition(motionLayout, foldFeature); ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold); } else { ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, 0); } } } } }
Функция fireNewValue() принимает идентификатор, представляющий значение, в качестве первого параметра и значение, которое нужно вставить, в качестве второго параметра.
ReactiveGuide

Один из способов использовать SharedValue в макете без написания кода – использовать вспомогательный класс ReactiveGuide. Это позволит разместить горизонтальную или вертикальную направляющую в соответствии со связанным SharedValue.
<androidx.constraintlayout.widget.ReactiveGuide
android:id="@+id/fold"
app:reactiveGuide_valueId="@id/fold"
android:orientation="horizontal" />
После этого ее можно использовать как обычную направляющую.
MotionLayout для складных устройств
В MotionLayout 2.1 мы добавили несколько функций, которые помогают изменять состояние. Это особенно полезно для складных устройств, поскольку обычно нам приходится обрабатывать анимацию между различными возможными макетами.
Для складных устройств есть два подхода:
- Во время выполнения обновите текущий макет (
ConstraintSet), чтобы показать или скрыть сгиб. - Используйте отдельный
ConstraintSetдля каждого из поддерживаемых состояний складного устройства (closed,foldedилиfully open).
Анимация: ConstraintSet
Функция updateStateAnimate() в MotionLayout была добавлена в версии 2.1:
Kotlin
fun updateStateAnimate(stateId: Int, set: ConstraintSet, duration: Int)
Java
void updateStateAnimate(int stateId, ConstraintSet set, int duration);
Эта функция автоматически анимирует изменения при обновлении заданного ConstraintSet вместо немедленного обновления (которое можно выполнить с помощью updateState(stateId, constraintset)). Это позволяет обновлять пользовательский интерфейс на лету в зависимости от изменений, например от того, в каком состоянии находится складное устройство.
ReactiveGuide в MotionLayout
ReactiveGuide также поддерживает два полезных атрибута, если используется внутри MotionLayout:
app:reactiveGuide_animateChange="true|false"app:reactiveGuide_applyToAllConstraintSets="true|false"
Первый изменит текущий ConstraintSet и автоматически анимирует изменение. Второй вариант позволяет применить новое значение позиции ReactiveGuide ко всем ConstraintSet в MotionLayout. Типичный подход для складных устройств – использовать ReactiveGuide, представляющий положение сгиба, и настраивать элементы макета относительно ReactiveGuide.
Использование нескольких ConstraintSet для представления состояния складного устройства
Вместо того чтобы обновлять текущее состояние MotionLayout, вы можете создать отдельные состояния (включая closed, folded и fully open).

В этом случае вы можете использовать ReactiveGuide, чтобы представить сгиб, но у вас будет гораздо больше контроля (по сравнению с автоматической анимацией при обновлении текущего ConstraintSet) над тем, как каждое состояние будет переходить в другое.
При таком подходе в прослушивателе DeviceState вы просто указываете MotionLayout перейти в определенные состояния с помощью метода MotionLayout.transitionToState(stateId).