App faltbar erkennen

Große, aufgeklappte Displays und einzigartige aufgeklappte Displays ermöglichen neue Nutzererfahrungen auf faltbare Geräte. Wenn Sie Ihre App für faltbare Geräte optimieren möchten, verwenden Sie die Jetpack WindowManager-Bibliothek. Sie bietet eine API-Oberfläche für Fensterfunktionen von faltbaren Geräten wie Scharniere und Falze. Wenn deine App „faltbar“ ist, kann sie ihr Layout anpassen Platzieren Sie wichtige Inhalte nicht im Bereich von Falten oder Scharnieren und verwenden Sie Falten. und Scharniere als natürliche Trennzeichen.

Wenn Sie wissen, ob ein Gerät Konfigurationen wie die Tisch- oder Buchhaltung unterstützt, können Sie Entscheidungen über die Unterstützung verschiedener Layouts oder die Bereitstellung bestimmter Funktionen treffen.

Fensterinformationen

Die WindowInfoTracker-Oberfläche in Jetpack WindowManager stellt Informationen zum Fensterlayout bereit. Die Methode windowLayoutInfo() der Schnittstelle gibt ein Stream von WindowLayoutInfo-Daten, die deine App über ein faltbares Gerät informieren des zusammengeklappten Geräts angezeigt. Mit der Methode WindowInfoTracker#getOrCreate() wird eine Instanz von WindowInfoTracker erstellt.

WindowManager unterstützt die Erfassung von WindowLayoutInfo-Daten mithilfe von Kotlin-Abläufe und Java-Callbacks

Kotlin-Datenflüsse

Um die Datenerhebung für WindowLayoutInfo zu starten und zu beenden, kannst du eine neustartfähige lebenszyklusbezogene Koroutine, in der der Codeblock repeatOnLifecycle wird ausgeführt, wenn der Lebenszyklus mindestens STARTED beträgt und beendet wird, wenn der Lebenszyklus ist STOPPED. Ausführung des Codeblocks wird automatisch neu gestartet wenn der Lebenszyklus wieder STARTED ist. Im folgenden Beispiel enthält der Codeblock erhebt und verwendet WindowLayoutInfo-Daten:

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 in der Abhängigkeit androidx.window:window-java kannst du WindowLayoutInfo-Updates erfassen, ohne einen Kotlin-Ablauf zu verwenden. Das Artefakt enthält der WindowInfoTrackerCallbackAdapter-Klasse, die ein WindowInfoTracker, um das Registrieren (und Aufheben der Registrierung) von Rückrufen an WindowLayoutInfo-Updates erhalten, zum Beispiel:

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 Sie RxJava (Version 2 oder 3) bereits verwenden, können Sie Artefakte nutzen, mit denen Sie Observable oder Flowable um WindowLayoutInfo-Updates ohne Kotlin-Ablauf zu erfassen.

Die von androidx.window:window-rxjava2 und zur Verfügung gestellte Kompatibilitätsebene androidx.window:window-rxjava3-Abhängigkeiten enthalten den WindowInfoTracker#windowLayoutInfoFlowable() und WindowInfoTracker#windowLayoutInfoObservable()-Methoden, mit denen Sie App, um WindowLayoutInfo-Updates zu erhalten, zum Beispiel:

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 Klasse WindowLayoutInfo von Jetpack WindowManager stellt die Funktionen eines Anzeigefenster als Liste mit DisplayFeature-Elementen verfügbar.

Ein FoldingFeature ist eine Art von DisplayFeature, die Informationen zu faltbaren Displays enthält, darunter:

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

  • orientation: Die Ausrichtung des Scharniers, HORIZONTAL oder VERTICAL

  • occlusionType: Ob das Fold oder Scharnier einen Teil des Displays verdeckt, NONE oder FULL

  • isSeparating: Gibt an, ob durch die Faltung oder das Scharnier zwei logische Darstellungsbereiche erzeugt werden, Wahr oder falsch?

Ein faltbares Gerät, das HALF_OPENED ist, meldet immer isSeparating als wahr, da das Display in zwei Displaybereiche unterteilt ist. Außerdem ist isSeparating ist auf einem Dual Screen-Gerät immer „true“, wenn die App sowohl Bildschirmen.

Die Property FoldingFeature bounds (übernommen von DisplayFeature) stellt das Begrenzungsrechteck eines Faltelements dar, z. B. eines Faltelements oder Scharniers. Anhand der Begrenzungen können Sie Elemente auf dem Bildschirm relativ zur Funktion positionieren:

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.
            }
        }
    }
}

Tischaufstellung

Mithilfe der im FoldingFeature-Objekt enthaltenen Informationen kann Ihre App wie eine Tischplatte, das Smartphone auf einer Oberfläche liegt, das Scharnier in horizontaler Position halten und das faltbare Display zur Hälfte aufgeklappt ist.

Mit der Funktion „Auf dem Tisch“ können Nutzende ihr Smartphone ohne das Smartphone in den Händen halten. Die Tischhaltung eignet sich hervorragend zum Ansehen von Medien. Fotos machen und Videoanrufe starten.

Abbildung 1. Eine Videoplayer-App im Modus „Auf dem Tisch“.

Mit FoldingFeature.State und FoldingFeature.Orientation können Sie Ob das Gerät auf dem Tisch steht:

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

Wenn Sie wissen, dass sich das Gerät in der Position „Auf dem Tisch“ befindet, aktualisieren Sie das App-Layout entsprechend. Bei Medien-Apps bedeutet dies in der Regel, dass die Wiedergabe über dem Steuerelemente zum Falten und Positionieren sowie ergänzende Inhalte Wiedergabe per Sprachbefehl steuern.

Unter Android 15 (API-Ebene 35) und höher können Sie eine synchrone API aufrufen, um unabhängig vom aktuellen Status des Geräts zu ermitteln, ob es die Positionierung auf einem Tisch unterstützt.

Die API enthält eine Liste der vom Gerät unterstützten Körperhaltungen. Wenn die Liste die Position „Auf dem Tablet“ enthält, können Sie Ihr App-Layout so aufteilen, dass es diese Position unterstützt, und A/B-Tests für die App-Benutzeroberfläche für die Positionen „Auf dem Tablet“ und „Vollbild“ ausfü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

Buchhaltungsstatus

Eine weitere einzigartige faltbare Funktion ist die Buchhaltung, bei der das Gerät zur Hälfte aufgeklappt ist. und das Scharnier ist vertikal. Die Lesehaltung eignet sich hervorragend zum Lesen von E-Books. Mit einem zweiseitigen Layout auf einem großen Display, das sich wie ein gebundenes Buch öffnen lässt, vermittelt die Buchhaltung das Gefühl, ein echtes Buch zu lesen.

Sie können sie auch für Fotos verwenden, wenn Sie per Sprachbefehl ein anderes Seitenverhältnis festlegen möchten.

Implementieren Sie die Buchhaltung mit denselben Techniken, die auch für die Tischhaltung verwendet werden. Der einzige Unterschied besteht darin, dass der Code prüfen sollte, ob die Ausrichtung der Faltfunktion 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 Gerätekonfiguration ändern, z. B. wenn das Gerät auf- oder zugeklappt, gedreht oder ein Fenster im Multifenstermodus neu skaliert wird.

Mit der Jetpack-WindowManager-Klasse WindowMetricsCalculator können Sie aktuelle und maximale Fenstermesswerte abrufen. Wie die Plattform Mit WindowMetrics in API-Ebene 30 wurde die WindowManager-Funktion WindowMetrics stellen die Fenstergrenzen bereit, aber die API ist abwärtskompatibel bis API-Level 14.

Weitere Informationen finden Sie unter Fenstergrößenklassen verwenden.

Weitere Informationen

Produktproben

  • Jetpack WindowManager: Beispiel für die Verwendung von Jetpack WindowManager-Bibliothek
  • Jetcaster : Implementierung des Tabellenstatus mit Compose

Codelabs