Concevoir pour les appareils pliables

Dans la version 2.1 de ConstraintLayout, plusieurs fonctionnalités ont été ajoutées pour faciliter la gestion des appareils pliables, y compris SharedValues et ReactiveGuide, ainsi qu'une meilleure compatibilité avec les animations avec MotionLayout.

Valeurs partagées

Nous avons ajouté un nouveau mécanisme pour injecter des valeurs d'exécution dans ConstraintLayout. Il est destiné à être utilisé pour les valeurs à l'échelle du système, car toutes les instances de ConstraintLayout peuvent accéder à la valeur.

Dans le contexte des appareils pliables, nous pouvons utiliser ce mécanisme pour injecter la position du pli au moment de l'exécution:

Kotlin

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

Java

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

Dans un assistant personnalisé, vous pouvez accéder aux valeurs partagées en ajoutant un écouteur pour toutes les modifications:

Kotlin

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

Java

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

Consultez l'exemple PoldableExperiments pour voir comment capturer la position du pli à l'aide de la bibliothèque Jetpack WindowManager et injecter la position dans 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() utilise un ID représentant la valeur comme premier paramètre et la valeur à injecter comme deuxième paramètre.

ReactiveGuide

Pour tirer parti d'un SharedValue dans une mise en page, sans avoir à écrire de code, vous pouvez utiliser l'outil d'aide ReactiveGuide. Cela permet de positionner une ligne horizontale ou verticale en fonction de l'élément SharedValue associé.

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

Vous pouvez ensuite l'utiliser comme vous le feriez normalement.

MotionLayout pour les appareils pliables

Nous avons ajouté plusieurs fonctionnalités dans MotionLayout dans la version 2.1 qui permettent de modifier l'état, ce qui est particulièrement utile pour les pliables, car nous devons généralement gérer l'animation entre les différentes mises en page possibles.

Deux approches sont disponibles pour les appareils pliables:

  • Au moment de l'exécution, mettez à jour votre mise en page actuelle (ConstraintSet) pour afficher ou masquer le pli.
  • Utilisez un ConstraintSet distinct pour chacun des états d'appareils pliables que vous souhaitez prendre en charge (closed, folded ou fully open).

Animer un ConstraintSet

La fonction updateStateAnimate() dans MotionLayout a été ajoutée dans la version 2.1:

Kotlin

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

Java

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

Cette fonction animera automatiquement les modifications lors de la mise à jour d'un ConstraintSet donné au lieu d'effectuer une mise à jour immédiate (ce que vous pouvez faire avec updateState(stateId, constraintset)). Cela vous permet de mettre à jour votre UI à la volée, en fonction des modifications, comme l'état du pliable dans lequel vous vous trouvez.

ReactiveGuide dans un élément MotionLayout

ReactiveGuide accepte également deux attributs utiles lorsqu'il est utilisé dans un MotionLayout:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

Le premier modifie le ConstraintSet actuel et anime ce changement automatiquement. Le second appliquera la nouvelle valeur de la position ReactiveGuide à tous les ConstraintSet de MotionLayout. Une approche typique pour les appareils pliables consiste à utiliser un ReactiveGuide représentant la position de pliage, en configurant vos éléments de mise en page par rapport à la ReactiveGuide.

Utiliser plusieurs ConstraintSet pour représenter l'état d'un appareil pliable

Au lieu de mettre à jour l'état MotionLayout actuel, une autre façon de concevoir votre UI pour qu'elle soit compatible avec les pliables consiste à créer des états distincts spécifiques (y compris closed, folded et fully open).

Dans ce scénario, vous pouvez toujours utiliser un ReactiveGuide pour représenter le pli, mais vous auriez beaucoup plus de contrôle (par rapport à l'animation automatisée lors de la mise à jour du ConstraintSet actuel) sur la façon dont chaque état passerait à un autre.

Avec cette approche, dans votre écouteur DeviceState, il vous suffit de demander à MotionLayout de passer à des états spécifiques via la méthode MotionLayout.transitionToState(stateId).