Cómo diseñar para dispositivos plegables

En la ConstraintLayout 2.1, se agregaron varias funciones para ayudar administrar dispositivos plegables, como SharedValues, ReactiveGuide y la compatibilidad mejorada con las animaciones con MotionLayout.

Valores compartidos

Agregamos un mecanismo nuevo para insertar valores de entorno de ejecución en ConstraintLayout: está diseñado para usarse con valores en todo el sistema, ya que todas las instancias de ConstraintLayout pueden acceder al valor.

En el contexto de los dispositivos plegables, podemos usar este mecanismo para inyectar la posición del pliegue en el tiempo de ejecución:

Kotlin

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

Java

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

En un asistente personalizado, puedes acceder a los valores compartidos agregando un objeto de escucha para cualquier cambio:

Kotlin

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

Java

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

Consulta el ejemplo de FoldableExperiments. para ver cómo capturamos la posición del pliegue con la Biblioteca de Jetpack WindowManager e inyectar la posición en 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() toma un ID que representa el valor como primer parámetro. el valor que se insertará como segundo parámetro.

ReactiveGuide

Una forma de aprovechar SharedValue en un diseño sin tener que hacerlo escribir código es usar ReactiveGuide como un asistente de chat. Esto posicionará una guía horizontal o vertical según el vinculado SharedValue.

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

Podrás usarla como lo harías con una guía normal.

MotionLayout para dispositivos plegables

Agregamos varias funciones a MotionLayout en 2.1 que ayudan a algo particularmente útil para los dispositivos plegables, ya que solemos tendrás que controlar la animación entre los diferentes diseños posibles.

Hay dos enfoques disponibles para los dispositivos plegables:

  • Durante el tiempo de ejecución, actualiza el diseño actual (ConstraintSet) para ocultar o mostrar el elemento de la línea de plegado.
  • Usa un ConstraintSet independiente para cada uno de los estados de dispositivo plegable que quieras (closed, folded o fully open).

Cómo animar un ConstraintSet

La función updateStateAnimate() en MotionLayout se agregó en la versión 2.1 lanzamiento:

Kotlin

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

Java

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

Esta función animará automáticamente los cambios cuando se actualice un ConstraintSet en lugar de realizar una actualización inmediata (lo que puedes hacer con updateState(stateId, constraintset)). Esto te permite actualizar la IU en la mosca, según los cambios, como el estado plegable en el que te encuentres.

ReactiveGuide dentro de una MotionLayout

ReactiveGuide también admite dos atributos útiles cuando se usa dentro de un MotionLayout

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

El primero modificará el ConstraintSet actual y animará el cambio. automáticamente. La segunda aplicará el valor nuevo de ReactiveGuide. a todos los ConstraintSet de MotionLayout. Un enfoque típico para dispositivos plegables sería usar un ReactiveGuide que represente la posición del pliegue. la configuración de tus elementos de diseño en relación con el ReactiveGuide

Cómo usar varios ConstraintSet para representar el estado plegable

En lugar de actualizar el estado actual de MotionLayout, otra forma de diseñar la arquitectura para dispositivos plegables con dispositivos plegables es crear estados separados específicos (como closed, folded y fully open).

En este caso, es posible que quieras usar un ReactiveGuide para representar el pero tendrías mucho más control (en comparación con la al actualizar el ConstraintSet actual) en cómo se vería cada estado transición a otra.

Con este enfoque, en tu objeto de escucha DeviceState, simplemente diriges el elemento MotionLayout para hacer la transición a estados específicos mediante la MotionLayout.transitionToState(stateId) .