Concevoir pour les appareils pliables

Dans la ConstraintLayout version 2.1, plusieurs fonctionnalités ont été ajoutées pour gérer les appareils pliables, y compris SharedValues, ReactiveGuide et amélioration de la prise en charge des 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 peut accéder à la valeur.

Dans le contexte des appareils pliables, ce mécanisme permet d'injecter de la ligne de flottaison lors 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 toute modification:

Kotlin

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

Java

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

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

ReactiveGuide

Une façon de profiter d'un SharedValue dans une mise en page, sans avoir à écrire du code, consiste à utiliser ReactiveGuide . Cela permet de placer une ligne de référence horizontale ou verticale conformément aux 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 facilitent le morphing particulièrement utile pour les appareils pliables, 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 la plier.
  • Utilisez un ConstraintSet distinct pour chacun des états de l'appareil pliable que vous souhaitez (closed, folded ou fully open).

Animer un ConstraintSet

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

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'une ConstraintSet 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 sur en fonction des changements, comme l'état de votre appareil pliable.

ReactiveGuide à l'intérieur d'un MotionLayout

ReactiveGuide prend également en charge deux attributs utiles lorsqu'il est utilisé dans un MotionLayout:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

Le premier modifiera le ConstraintSet actuel et animera la modification automatiquement. La seconde applique la nouvelle valeur de ReactiveGuide à tous les ConstraintSet dans MotionLayout. Une approche typique pour pour les pliables, il faudrait utiliser ReactiveGuide pour représenter la position de pliage, configurer vos éléments de mise en page par rapport à ReactiveGuide.

Utiliser plusieurs éléments ConstraintSet pour représenter l'état des appareils pliables

Au lieu de mettre à jour l'état MotionLayout actuel, vous pouvez structurer votre UI pour prendre en charge les appareils 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 mais vous bénéficiez d'un contrôle bien plus important lors de la mise à jour du ConstraintSet actuel) sur la façon dont chaque état passer à un autre.

Avec cette approche, dans votre écouteur DeviceState, vous dirigeriez simplement MotionLayout pour passer à des états spécifiques via la MotionLayout.transitionToState(stateId) .