Projektowanie urządzeń składanych

W wersji ConstraintLayout 2.1 dodaliśmy kilka funkcji, które ułatwiają zarządzanie urządzeniami składanymi, w tym SharedValues i ReactiveGuide oraz ulepszoną obsługę animacji w wersji MotionLayout.

Wspólne wartości

Dodaliśmy nowy mechanizm wstrzykiwania wartości środowiska wykonawczego w komponencie ConstraintLayout. Jest on przeznaczony dla wartości w całym systemie, ponieważ dostęp do wartości mają wszystkie instancje funkcji ConstraintLayout.

W przypadku urządzeń składanych możemy wykorzystać ten mechanizm, aby określić położenie składane w czasie działania:

Kotlin

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

Java

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

W niestandardowym interfejsie pomocniczym możesz uzyskać dostęp do wspólnych wartości, dodając odbiornik zmian:

Kotlin

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

Java

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

Zapoznaj się z przykładem aplikacji Foldable Experiments, aby zobaczyć, jak za pomocą biblioteki Jetpack WindowManager rejestrujemy położenie strony widocznej na ekranie i wprowadzamy ją w elemencie 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() pobiera identyfikator reprezentujący wartość jako pierwszy parametr i wartość do wstrzykiwania jako drugi parametr.

ReactiveGuide

Jednym ze sposobów wykorzystania elementu SharedValue w układzie bez konieczności pisania kodu jest skorzystanie z elementu pomocniczego ReactiveGuide. Spowoduje to ustawienie pozycji poziomej lub pionowej wskazówki zgodnie z połączonym SharedValue.

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

Możesz go używać tak samo jak ze standardowymi wytycznymi.

MotionLayout – urządzenia składane

W usłudze MotionLayout w wersji 2.1 dodaliśmy kilka funkcji, które ułatwiają przekształcanie urządzeń, co przydaje się zwłaszcza w przypadku urządzeń składanych, ponieważ zazwyczaj musimy obsługiwać animowanie między różnymi możliwymi układami.

W przypadku urządzeń składanych dostępne są 2 sposoby:

  • W czasie działania zaktualizuj bieżący układ (ConstraintSet), aby wyświetlić lub ukryć część strony widoczną na ekranie.
  • Użyj osobnego elementu ConstraintSet w przypadku każdego stanu składanego, który chcesz obsługiwać (closed, folded lub fully open).

Optymalizuję: ConstraintSet

Funkcja updateStateAnimate() w MotionLayout została dodana w wersji 2.1:

Kotlin

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

Java

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

Ta funkcja automatycznie animuje zmiany podczas aktualizowania danego elementu ConstraintSet, zamiast przeprowadzać natychmiastową aktualizację (możesz to zrobić za pomocą updateState(stateId, constraintset)). Dzięki temu możesz aktualizować interfejs na bieżąco w zależności od zmian, takich jak Twój stan składany.

ReactiveGuide w: MotionLayout

Atrybut ReactiveGuide [MotionLayout] obsługuje też 2 przydatne atrybuty:

  • app:reactiveGuide_animateChange="true|false"

  • app:reactiveGuide_applyToAllConstraintSets="true|false"

Pierwszy z nich zmodyfikuje bieżące ConstraintSet i automatycznie animuje zmianę. Drugie rozwiązanie zastosuje nową wartość pozycji ReactiveGuide do wszystkich elementów ConstraintSet w: MotionLayout. W przypadku urządzeń składanych typowym podejściem jest użycie obiektu ReactiveGuide reprezentującego pozycję strony widocznej po przewinięciu i ustawienie elementów układu względem elementu ReactiveGuide.

Używanie wielu elementów ConstraintSet do reprezentowania stanu składanego

Zamiast aktualizować bieżący stan MotionLayout, innym sposobem zaprojektowania interfejsu pod kątem obsługi urządzeń składanych jest utworzenie określonych osobnych stanów (w tym closed, folded i fully open).

W tej sytuacji warto użyć elementu ReactiveGuide do reprezentowania części strony widocznej na ekranie, ale będziesz mieć znacznie większą kontrolę (w porównaniu z automatyczną animacją podczas aktualizacji bieżącej ConstraintSet) nad sposobem przejścia poszczególnych stanów do innego.

Przy takim podejściu w detektorze DeviceState będziesz po prostu kierować element MotionLayout do określonych stanów za pomocą metody MotionLayout.transitionToState(stateId).