Проектирование складных устройств

В выпуске ConstraintLayout 2.1 было добавлено несколько функций, помогающих управлять складными устройствами , включая SharedValues , ReactiveGuide и расширенную поддержку анимации с помощью MotionLayout .

Общие ценности

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

В контексте складных устройств мы можем использовать этот механизм для определения положения сгиба во время выполнения:

Котлин

ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold)

Ява

ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold);

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

Котлин

val sharedValues: SharedValues = ConstraintLayout.getSharedValues()
sharedValues.addListener(mAttributeId, this)

Ява

SharedValues sharedValues = ConstraintLayout.getSharedValues();
sharedValues.addListener(mAttributeId, this);

Вы можете посмотреть пример FoldableExperiments , чтобы увидеть, как мы фиксируем положение сгиба с помощью библиотеки Jetpack WindowManager и вводим это положение в ConstraintLayout .

Котлин

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);
                }
            }
        }
    }
}

Ява

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:

Котлин

fun updateStateAnimate(stateId: Int, set: ConstraintSet, duration: Int)

Ява

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) .