Designs für faltbare Smartphones und Tablets entwickeln

In Version 2.1 von ConstraintLayout wurden mehrere Funktionen zur Verwaltung von faltbaren Geräten hinzugefügt, darunter SharedValues und ReactiveGuide. Außerdem wurde die Unterstützung für Animationen mit MotionLayout verbessert.

Gemeinsame Werte

Wir haben einen neuen Mechanismus zum Einfügen von Laufzeitwerten in ConstraintLayout hinzugefügt. Er soll für systemweite Werte verwendet werden, da alle Instanzen von ConstraintLayout auf den Wert zugreifen können.

Im Kontext faltbarer Geräte können wir diesen Mechanismus verwenden, um die Position des Scrollens während der Laufzeit zu übernehmen:

Kotlin

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

Java

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

In einem benutzerdefinierten Hilfsprogramm können Sie auf die gemeinsamen Werte zugreifen. Dazu fügen Sie für alle Änderungen einen Listener hinzu:

Kotlin

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

Java

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

Das FoldableExperiments-Beispiel zeigt, wie wir die Position des Scrollens mithilfe der Jetpack WindowManager-Bibliothek erfassen und in ConstraintLayout einfügen.

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);
                }
            }
        }
    }
}

Für fireNewValue() wird eine ID verwendet, die den Wert als ersten Parameter und den als zweiten Parameter einzufügenden Wert darstellt.

ReactiveGuide

Eine Möglichkeit, die Vorteile von SharedValue in einem Layout zu nutzen, ohne Code schreiben zu müssen, besteht darin, das Helper-Tool ReactiveGuide zu verwenden. Dadurch wird eine horizontale oder vertikale Richtlinie gemäß der verknüpften SharedValue positioniert.

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

Sie können es dann wie gewohnt mit einer normalen Richtlinie verwenden.

MotionLayout für faltbare Smartphones

In Version 2.1 haben wir mehrere Funktionen für MotionLayout hinzugefügt, die das Morphen des Zustands erleichtern. Das ist besonders für faltbare Smartphones nützlich, da wir normalerweise die Animation zwischen den verschiedenen möglichen Layouts handhaben müssen.

Für faltbare Smartphones gibt es zwei Ansätze:

  • Aktualisieren Sie zur Laufzeit das aktuelle Layout (ConstraintSet), um das Layout ein- oder auszublenden.
  • Verwende eine separate ConstraintSet für jeden faltbaren Status, den du unterstützen möchtest (closed, folded oder fully open).

ConstraintSet wird animiert

Die Funktion updateStateAnimate() in MotionLayout wurde in Version 2.1 hinzugefügt:

Kotlin

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

Java

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

Mit dieser Funktion werden die Änderungen beim Aktualisieren eines bestimmten ConstraintSet automatisch animiert, anstatt ein sofortiges Update durchzuführen (was mit updateState(stateId, constraintset) möglich ist). So können Sie Ihre Benutzeroberfläche im Handumdrehen aktualisieren, je nach Änderungen, z. B. in welchem faltbaren Zustand Sie sich befinden.

ReactiveGuide in einem MotionLayout

ReactiveGuide unterstützt außerdem zwei nützliche Attribute, wenn es innerhalb einer MotionLayout verwendet wird:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

Mit dem ersten wird der aktuelle ConstraintSet geändert und die Änderung automatisch animiert. Die zweite wendet den neuen Wert der Position ReactiveGuide auf alle ConstraintSets in MotionLayout an. Ein typischer Ansatz für faltbare Smartphones wäre, ein ReactiveGuide-Element zu verwenden, das die Faltposition darstellt. Dabei werden die Layoutelemente relativ zum ReactiveGuide eingerichtet.

Mehrere ConstraintSets zur Darstellung des faltbaren Zustands verwenden

Anstatt den aktuellen MotionLayout-Status zu aktualisieren, können Sie Ihre UI für die Unterstützung faltbarer Geräte auch erstellen, indem Sie bestimmte separate Status (einschließlich closed, folded und fully open) erstellen.

In diesem Szenario sollten Sie möglicherweise trotzdem ein ReactiveGuide verwenden, um den „Fold“ darzustellen. Allerdings hätten Sie (im Vergleich zur automatischen Animation beim Aktualisieren des aktuellen ConstraintSet) viel mehr Kontrolle darüber, wie jeder Zustand in einen anderen übergeht.

Bei diesem Ansatz leiten Sie MotionLayout in Ihrem DeviceState-Listener einfach über die Methode MotionLayout.transitionToState(stateId) an, in bestimmte Zustände zu wechseln.