App faltbar erkennen

Große, aufgeklappte Displays und einzigartige zugeklappte Zustände ermöglichen neue Nutzererlebnisse auf faltbaren Geräten. Wenn deine App auf faltbare Geräte reagieren soll, verwende die Jetpack WindowManager Bibliothek. Sie bietet eine API-Oberfläche für Fensterfunktionen faltbarer Geräte wie Faltungen und Scharniere. Wenn deine App auf Faltungen reagiert, kann sie ihr Layout so anpassen, dass wichtige Inhalte nicht im Bereich von Faltungen oder Scharnieren platziert werden. Faltungen und Scharniere können als natürliche Trennlinien verwendet werden.

Wenn du weißt, ob ein Gerät Konfigurationen wie den Modus „Auf dem Tisch“ oder den Buch modus unterstützt, kannst du besser entscheiden, ob du verschiedene Layouts unterstützen oder bestimmte Funktionen anbieten solltest.

Fensterinformationen

Die WindowInfoTracker-Schnittstelle in Jetpack WindowManager stellt Informationen zum Fenster layout bereit. Die Methode windowLayoutInfo() der Schnittstelle gibt einen Stream von WindowLayoutInfo-Daten zurück, die deine App über den Faltungszustand eines faltbaren Geräts informieren. Mit der WindowInfoTracker#getOrCreate() Methode wird eine Instanz von WindowInfoTracker erstellt.

WindowManager unterstützt die Erfassung von WindowLayoutInfo Daten mithilfe von Kotlin-Flows und Java-Callbacks.

Kotlin-Flows

Um die Erfassung von WindowLayoutInfo Daten zu starten und zu beenden, kannst du eine neu startbare lebenszyklusbezogene Coroutine verwenden, in der der repeatOnLifecycle Codeblock ausgeführt wird, wenn der Lebenszyklus mindestens STARTED ist, und beendet wird, wenn der Lebenszyklus STOPPED ist. Die Ausführung des Codeblocks wird automatisch neu gestartet wenn der Lebenszyklus wieder STARTED ist. Im folgenden Beispiel werden `WindowLayoutInfo`-Daten erfasst und verwendet:WindowLayoutInfo

class DisplayFeaturesActivity : AppCompatActivity() {

    private lateinit var binding: ActivityDisplayFeaturesBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
        setContentView(binding.root)

        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
                    .windowLayoutInfo(this@DisplayFeaturesActivity)
                    .collect { newLayoutInfo ->
                        // Use newLayoutInfo to update the layout.
                    }
            }
        }
    }
}

Java-Callbacks

Mit der Callback-Kompatibilitätsebene, die in der androidx.window:window-java Abhängigkeit enthalten ist, kannst du WindowLayoutInfo Updates erfassen, ohne einen Kotlin-Flow zu verwenden. Das Artefakt enthält die WindowInfoTrackerCallbackAdapter-Klasse, die ein WindowInfoTracker anpasst, um die Registrierung (und Aufhebung der Registrierung) von Callbacks zu unterstützen, die WindowLayoutInfo-Updates empfangen, z. B.:

public class SplitLayoutActivity extends AppCompatActivity {

    private WindowInfoTrackerCallbackAdapter windowInfoTracker;
    private ActivitySplitLayoutBinding binding;
    private final LayoutStateChangeCallback layoutStateChangeCallback =
            new LayoutStateChangeCallback();

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

       windowInfoTracker =
                new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
   }

   @Override
   protected void onStart() {
       super.onStart();
       windowInfoTracker.addWindowLayoutInfoListener(
                this, Runnable::run, layoutStateChangeCallback);
   }

   @Override
   protected void onStop() {
       super.onStop();
       windowInfoTracker
           .removeWindowLayoutInfoListener(layoutStateChangeCallback);
   }

   class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
       @Override
       public void accept(WindowLayoutInfo newLayoutInfo) {
           SplitLayoutActivity.this.runOnUiThread( () -> {
               // Use newLayoutInfo to update the layout.
           });
       }
   }
}

RxJava-Unterstützung

Wenn du bereits RxJava (Version 2 oder 3) verwendest, kannst du Artefakte nutzen, mit denen du ein Observable oder Flowable verwenden kannst, um WindowLayoutInfo Updates zu erfassen, ohne einen Kotlin-Flow zu verwenden.

Die Kompatibilitätsebene, die von den androidx.window:window-rxjava2 und androidx.window:window-rxjava3 Abhängigkeiten bereitgestellt wird, enthält die WindowInfoTracker#windowLayoutInfoFlowable() und WindowInfoTracker#windowLayoutInfoObservable() Methoden, mit denen deine App WindowLayoutInfo Updates empfangen kann, z. B.:

class RxActivity: AppCompatActivity {

    private lateinit var binding: ActivityRxBinding

    private var disposable: Disposable? = null
    private lateinit var observable: Observable<WindowLayoutInfo>

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

        // Create a new observable.
        observable = WindowInfoTracker.getOrCreate(this@RxActivity)
            .windowLayoutInfoObservable(this@RxActivity)
   }

   @Override
   protected void onStart() {
       super.onStart();

        // Subscribe to receive WindowLayoutInfo updates.
        disposable?.dispose()
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { newLayoutInfo ->
            // Use newLayoutInfo to update the layout.
        }
   }

   @Override
   protected void onStop() {
       super.onStop();

        // Dispose of the WindowLayoutInfo observable.
        disposable?.dispose()
   }
}

Funktionen faltbarer Displays

Die WindowLayoutInfo Klasse von Jetpack WindowManager stellt die Funktionen eines Anzeigefensters als Liste von DisplayFeature Elementen zur Verfügung.

Ein FoldingFeature ist ein Typ von DisplayFeature, der Informationen zu faltbaren Displays enthält, einschließlich der folgenden Attribute:

  • state: Der zugeklappte Zustand des Geräts, FLAT oder HALF_OPENED

  • orientation: Die Ausrichtung der Faltung oder des Scharniers, HORIZONTAL oder VERTICAL

  • occlusionType: Gibt an, ob die Faltung oder das Scharnier einen Teil des Displays verdeckt, NONE oder FULL

  • isSeparating: Gibt an, ob die Faltung oder das Scharnier zwei logische Anzeigebereiche erstellt, „true“ oder „false“

Bei einem faltbaren Gerät, das HALF_OPENED ist, wird isSeparating immer als „true“ gemeldet , da der Bildschirm in zwei Anzeigebereiche unterteilt ist. Außerdem ist isSeparating auf einem Gerät mit zwei Bildschirmen immer „true“, wenn die Anwendung beide Bildschirme umfasst.

Das Attribut FoldingFeature bounds (geerbt von DisplayFeature) stellt das umgebende Rechteck einer Faltungsfunktion wie einer Faltung oder eines Scharniers dar. Mit den Grenzen können Elemente auf dem Bildschirm relativ zur Funktion positioniert werden:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    lifecycleScope.launch(Dispatchers.Main) {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // Safely collects from WindowInfoTracker when the lifecycle is
            // STARTED and stops collection when the lifecycle is STOPPED.
            WindowInfoTracker.getOrCreate(this@MainActivity)
                .windowLayoutInfo(this@MainActivity)
                .collect { layoutInfo ->
                    // New posture information.
                    val foldingFeature = layoutInfo.displayFeatures
                        .filterIsInstance<FoldingFeature>()
                        .firstOrNull()
                    // Use information from the foldingFeature object.
                }
        }
    }
}

Java

private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private final LayoutStateChangeCallback layoutStateChangeCallback =
                new LayoutStateChangeCallback();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    // ...
    windowInfoTracker =
            new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}

@Override
protected void onStart() {
    super.onStart();
    windowInfoTracker.addWindowLayoutInfoListener(
            this, Runnable::run, layoutStateChangeCallback);
}

@Override
protected void onStop() {
    super.onStop();
    windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}

class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
    @Override
    public void accept(WindowLayoutInfo newLayoutInfo) {
        // Use newLayoutInfo to update the Layout.
        List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures();
        for (DisplayFeature feature : displayFeatures) {
            if (feature instanceof FoldingFeature) {
                // Use information from the feature object.
            }
        }
    }
}

Modus „Auf dem Tisch“

Mithilfe der Informationen im FoldingFeature-Objekt kann deine App Modi wie den Modus „Auf dem Tisch“ unterstützen, bei dem das Smartphone auf einer Oberfläche liegt, das Scharnier sich in horizontaler Position befindet und der faltbare Bildschirm halb geöffnet ist.

Im Modus „Auf dem Tisch“ können Nutzer ihre Smartphones bedienen, ohne sie in der Hand halten zu müssen. Der Modus „Auf dem Tisch“ eignet sich hervorragend zum Ansehen von Medien, zum Aufnehmen von Fotos und für Videoanrufe.

Abbildung 1. Eine Videoplayer-App im Modus „Auf dem Tisch“: Video auf dem vertikalen Teil des Bildschirms, Wiedergabesteuerung auf dem horizontalen Teil.

Mit FoldingFeature.State und FoldingFeature.Orientation kannst du feststellen , ob sich das Gerät im Modus „Auf dem Tisch“ befindet:

Kotlin

fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}

Java

boolean isTableTopPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}

Sobald du weißt, dass sich das Gerät im Modus „Auf dem Tisch“ befindet, aktualisiere das Layout deiner App entsprechend. Bei Media-Apps bedeutet das in der Regel, dass die Wiedergabe über der Faltung platziert wird und die Steuerelemente und zusätzlichen Inhalte direkt darunter positioniert werden, um eine freihändige Wiedergabe zu ermöglichen.

Unter Android 15 (API-Level 35) und höher kannst du eine synchrone API aufrufen, um zu erkennen, ob ein Gerät den Modus „Auf dem Tisch“ unterstützt, unabhängig vom aktuellen Zustand des Geräts.

Die API bietet eine Liste der vom Gerät unterstützten Modi. Wenn die Liste den Modus „Auf dem Tisch“ enthält, kannst du das Layout deiner App aufteilen, um den Modus zu unterstützen und A/B-Tests für die Benutzeroberfläche deiner App für den Modus „Auf dem Tisch“ und den Vollbildmodus durchführen.

Kotlin

if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
    val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
    if (postures.contains(TABLE_TOP)) {
        // Device supports tabletop posture.
   }
}

Java

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
    if (postures.contains(SupportedPosture.TABLETOP)) {
        // Device supports tabletop posture.
    }
}

Beispiele

Buchmodus

Eine weitere einzigartige Funktion faltbarer Geräte ist der Buchmodus, bei dem das Gerät halb geöffnet ist und das Scharnier vertikal ausgerichtet ist. Der Buchmodus eignet sich hervorragend zum Lesen von E‑Books. Mit einem Layout mit zwei Seiten auf einem großen faltbaren Bildschirm, der wie ein gebundenes Buch geöffnet ist, vermittelt der Buch modus das Gefühl, ein echtes Buch zu lesen.

Er kann auch für die Fotografie verwendet werden, wenn du ein anderes Seitenverhältnis aufnehmen möchtest, während du freihändig fotografierst.

Implementiere den Buchmodus mit denselben Techniken wie den Modus „Auf dem Tisch“. Der einzige Unterschied besteht darin, dass der Code prüfen sollte, ob die Ausrichtung der Faltungsfunktion vertikal statt horizontal ist:

Kotlin

fun isBookPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
}

Java

boolean isBookPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL);
}

Änderungen der Fenstergröße

Der Anzeigebereich einer App kann sich aufgrund einer Änderung der Gerätekonfiguration ändern, z. B. wenn das Gerät gefaltet oder aufgeklappt, gedreht oder die Größe eines Fensters im Mehrfenstermodus geändert wird.

Mit der Klasse WindowMetricsCalculator von Jetpack WindowManager kannst du die aktuellen und maximalen Fenstermesswerte abrufen. Wie die Plattform WindowMetrics, die in API-Level 30 eingeführt wurde, bieten die WindowManager WindowMetrics die Fenstergrenzen. Die API ist jedoch abwärtskompatibel bis zu API-Level 14.

Weitere Informationen findest du unter Fenstergrößenklassen verwenden.

Zusätzliche Ressourcen

Produktproben

  • Jetpack WindowManager: Beispiel für die Verwendung der Jetpack WindowManager-Bibliothek
  • Jetcaster : Implementierung des Modus „Auf dem Tisch“ mit Compose

Codelabs