Progettazione di pieghevoli

Nella versione 2.1 di ConstraintLayout sono state aggiunte diverse funzionalità per agevolare la gestione dei dispositivi pieghevoli, tra cui SharedValues, ReactiveGuide e il supporto migliorato dell'animazione con MotionLayout.

Valori condivisi

Abbiamo aggiunto un nuovo meccanismo per inserire i valori di runtime in ConstraintLayout, da utilizzare per i valori a livello di sistema, poiché tutte le istanze di ConstraintLayout sono in grado di accedere al valore.

Nel contesto dei dispositivi pieghevoli, possiamo utilizzare questo meccanismo per inserire la posizione del fold in fase di runtime:

Kotlin

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

Java

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

In un helper personalizzato, puoi accedere ai valori condivisi aggiungendo un listener per qualsiasi modifica:

Kotlin

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

Java

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

Puoi consultare l'esempio di Foldable Experiments per vedere come acquisire la posizione del fold utilizzando la libreria Jetpack WindowManager e iniettare la posizione in 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() prende un ID che rappresenta il valore come primo parametro e il valore da inserire come secondo parametro.

ReactiveGuide

Un modo per sfruttare SharedValue in un layout senza dover scrivere alcun codice è utilizzare l'helper ReactiveGuide. In questo modo, verrà posizionata una linea guida orizzontale o verticale in base al SharedValue collegato.

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

Può quindi essere utilizzato come faresti con una normale linea guida.

MotionLayout per i pieghevoli

Abbiamo aggiunto diverse funzionalità in MotionLayout nella versione 2.1 che consentono il morphing dello stato, una funzionalità particolarmente utile per i pieghevoli, dato che in genere dobbiamo gestire l'animazione tra i diversi layout possibili.

Sono disponibili due approcci per i pieghevoli:

  • In fase di runtime, aggiorna il layout corrente (ConstraintSet) per mostrare o nascondere il pulsante.
  • Utilizza un elemento ConstraintSet separato per ogni stato pieghevole che vuoi supportare (closed, folded o fully open).

Animazione di un elemento ConstraintSet

La funzione updateStateAnimate() in MotionLayout è stata aggiunta nella release 2.1:

Kotlin

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

Java

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

Questa funzione anima automaticamente le modifiche durante l'aggiornamento di un determinato ConstraintSet anziché eseguire un aggiornamento immediato (puoi farlo con updateState(stateId, constraintset)). In questo modo puoi aggiornare la tua UI in tempo reale a seconda delle modifiche, ad esempio lo stato pieghevole in cui ti trovi.

ReactiveGuide in un MotionLayout

ReactiveGuide supporta anche due attributi utili quando utilizzato all'interno di un MotionLayout:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

Il primo modifica l'attuale ConstraintSet e anima automaticamente la modifica. Il secondo applicherà il nuovo valore della posizione ReactiveGuide a tutte le ConstraintSet in MotionLayout. Un approccio tipico per gli elementi pieghevoli consiste nell'utilizzare un ReactiveGuide che rappresenta la posizione di piegatura, configurando gli elementi del layout in base a ReactiveGuide.

Utilizzo di più ConstraintSet per rappresentare lo stato pieghevole

Anziché aggiornare l'attuale stato MotionLayout, un altro modo per progettare la tua UI per il supporto dei pieghevoli consiste nel creare stati separati specifici (inclusi closed, folded e fully open).

In questo scenario, potresti comunque voler utilizzare un ReactiveGuide per rappresentare il fold, ma avrai un controllo molto maggiore (rispetto all'animazione automatica quando si aggiorna l'attuale ConstraintSet) sul modo in cui ogni stato passerebbe a un altro.

Con questo approccio, per chi ascolta DeviceState puoi semplicemente indicare all'MotionLayout di passare a stati specifici usando il metodo MotionLayout.transitionToState(stateId).