Como desenvolver para dobráveis

No ConstraintLayout versão 2.1, vários recursos foram adicionados para ajudar gerenciar dispositivos dobráveis, incluindo SharedValues, ReactiveGuide e suporte aprimorado a animação com MotionLayout.

Valores compartilhados

Adicionamos um novo mecanismo para injetar valores de ambiente de execução em ConstraintLayout: Ela se destina a valores em todo o sistema, já que todas as instâncias de ConstraintLayout podem acessar o valor.

No contexto de dispositivos dobráveis, podemos usar esse mecanismo para injetar a posição da dobra no momento da execução:

Kotlin

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

Java

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

Em um assistente personalizado, você pode acessar os valores compartilhados adicionando um listener para alguma alteração:

Kotlin

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

Java

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

Consulte o exemplo do FoldableExperiments. para ver como capturamos a posição da dobra usando o Biblioteca Jetpack WindowManager e injetar a posição em 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() usa um ID que representa o valor como o primeiro parâmetro e o valor a ser injetado como o segundo parâmetro.

ReactiveGuide

Uma maneira de usar uma SharedValue em um layout, sem precisar escrever códigos, é usar o ReactiveGuide ajudante. Isso posicionará uma linha guia horizontal ou vertical de acordo com o vinculado a SharedValue.

    <androidx.constraintlayout.widget.ReactiveGuide
        android:id="@+id/fold"
        app:reactiveGuide_valueId="@id/fold"
        android:orientation="horizontal" />

Em seguida, ele pode ser usado como uma diretriz normal.

MotionLayout para dispositivos dobráveis

Adicionamos vários recursos à versão 2.1 do MotionLayout que ajudam na transformação algo útil para dispositivos dobráveis, já que precisa lidar com a animação entre os diferentes layouts possíveis.

Há duas abordagens disponíveis para dispositivos dobráveis:

  • Durante a execução, atualize o layout atual (ConstraintSet) para mostrar ou ocultar o dobra.
  • Use uma ConstraintSet separada para cada um dos estados dobráveis que você quer compatíveis (closed, folded ou fully open).

Como animar um ConstraintSet

A função updateStateAnimate() em MotionLayout foi adicionada à versão 2.1 lançamento:

Kotlin

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

Java

void updateStateAnimate(int stateId, ConstraintSet set, int duration);

Esta função animará automaticamente as mudanças ao atualizar um determinado ConstraintSet em vez de fazer uma atualização imediata, que você pode fazer com updateState(stateId, constraintset)). Isso permite atualizar a interface com facilidade, dependendo das mudanças, como em qual estado dobrável você está.

ReactiveGuide em um MotionLayout

ReactiveGuide também oferece suporte a dois atributos úteis quando usado dentro de um MotionLayout:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

O primeiro vai modificar o ConstraintSet atual e animar a mudança. automaticamente. O segundo vai aplicar o novo valor de ReactiveGuide. para todos os ConstraintSets no MotionLayout. Uma abordagem típica dobráveis seriam usar um ReactiveGuide para representar a posição da dobra. configurando os elementos de layout em relação à ReactiveGuide.

Como usar vários ConstraintSets para representar o estado dobrável

Em vez de atualizar o estado atual da MotionLayout, outra maneira de arquitetar que sua interface ofereça suporte a dispositivos dobráveis é criar estados separados específicos, incluindo closed, folded e fully open).

Nesse cenário, ainda convém usar um ReactiveGuide para representar o mas você teria muito mais controle (em comparação com as campanhas ao atualizar a ConstraintSet atual) sobre como cada estado passar para outro.

Com essa abordagem, no listener DeviceState, você simplesmente direcionaria o MotionLayout para fazer a transição para estados específicos usando o MotionLayout.transitionToState(stateId) .