En la versión 2.1 de ConstraintLayout
, se agregaron varias funciones para ayudar a administrar dispositivos plegables, incluidas SharedValues
y ReactiveGuide
, y la compatibilidad mejorada para animaciones con MotionLayout
.
Valores compartidos
Agregamos un nuevo mecanismo para insertar valores del entorno de ejecución en ConstraintLayout
, que 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 si agregas 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);
Puedes observar el ejemplo de FoldableExperiments para ver cómo capturamos la posición del pliegue con la biblioteca Jetpack WindowManager y cómo inyectamos 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 el primer parámetro y el valor que se insertará como el segundo parámetro.
ReactiveGuide
Una forma de aprovechar un SharedValue
en un diseño, sin tener que escribir ningún código, es usar el asistente ReactiveGuide
. De esta manera, se posicionará una guía horizontal o vertical según el SharedValue
vinculado.
<androidx.constraintlayout.widget.ReactiveGuide
android:id="@+id/fold"
app:reactiveGuide_valueId="@id/fold"
android:orientation="horizontal" />
Puedes usarlo como lo harías con una guía normal.
MotionLayout
para dispositivos plegables
Agregamos varias funciones en la versión 2.1 de MotionLayout
que ayudan a transformar el estado, algo particularmente útil para los dispositivos plegables, ya que, en general, debemos 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 tu diseño actual (
ConstraintSet
) para ocultar o mostrar el pliegue. - Usa un
ConstraintSet
independiente para cada uno de los estados plegables que desees admitir (closed
,folded
ofully open
).
Cómo animar un ConstraintSet
La función updateStateAnimate()
en MotionLayout
se agregó en la versión 2.1:
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
determinado, en lugar de realizar una actualización inmediata (lo que puedes hacer con updateState(stateId, constraintset)
). Esto te permite actualizar tu IU sobre la marcha, según los cambios, como el estado plegable en el que te encuentres.
ReactiveGuide
dentro de un 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 nuevo valor de la posición ReactiveGuide
a todos los ConstraintSet
en MotionLayout
. Un enfoque típico para los dispositivos plegables es usar un ReactiveGuide
que represente la posición de plegado y configure 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 tu IU para admitir dispositivos plegables es crear estados separados específicos (incluidos closed
, folded
y fully open
).
En esta situación, es posible que aún quieras usar un ReactiveGuide
para representar el pliegue, pero tendrías mucho más control (en comparación con la animación automática cuando se actualiza el ConstraintSet
actual) sobre la transición de cada estado a otro.
Con este enfoque, en tu objeto de escucha DeviceState
, simplemente debes indicar a MotionLayout
para que haga la transición a estados específicos a través del método MotionLayout.transitionToState(stateId)
.