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 faltbare Geräte 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 Tischmodus oder die Buchhaltung unterstützt, kannst du Entscheidungen zur Unterstützung verschiedener Layouts oder zur Bereitstellung bestimmter Funktionen treffen.
Fensterinformationen
Die WindowInfoTracker-Schnittstelle in Jetpack WindowManager stellt Informationen zum Fenster
layout bereit. Die windowLayoutInfo()-Methode 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 WindowLayoutInfo Datenerhebung zu starten und zu beenden, kannst du eine neu startbare lebenszyklusbezogene Koroutine 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 erfasst und verwendet der Codeblock WindowLayoutInfo-Daten:
class DisplayFeaturesActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { // ... 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 einen
WindowInfoTracker anpasst, um die Registrierung (und Aufhebung der Registrierung) von Callbacks zu unterstützen, um
WindowLayoutInfo Updates zu erhalten, 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 = ActivityRxBinding.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,FLAToderHALF_OPENEDorientation: Die Ausrichtung der Faltung oder des Scharniers,HORIZONTALoderVERTICALocclusionType: Gibt an, ob die Faltung oder das Scharnier einen Teil des Displays verdeckt,NONEoderFULLisSeparating: 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 (von DisplayFeature übernommen)
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.
}
}
}
}
Auf dem Tisch
Anhand der Informationen im FoldingFeature-Objekt kann deine App Modi wie den Tischmodus 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 Tischmodus können Nutzer ihre Smartphones bedienen, ohne sie in der Hand halten zu müssen. Der Tischmodus eignet sich hervorragend zum Ansehen von Medien, zum Aufnehmen von Fotos und für Videoanrufe.
Mit FoldingFeature.State und FoldingFeature.Orientation kannst du feststellen,
ob sich das Gerät im Tischmodus 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 Tischmodus 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 Tischmodus 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 Tischmodus 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 Tischmodus und das Vollbildlayout ausführen.
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 6) { val postures = WindowInfoTracker.getOrCreate(context).supportedPostures if (postures.contains(SupportedPosture.TABLETOP)) { // 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
MediaPlayerActivity-App: Hier erfährst du, wie du mit Media3 Exoplayer und WindowManager einen Videoplayer erstellen kannst, der auf faltbare Geräte reagiert.Kamera-App auf faltbaren Geräten mit Jetpack WindowManager optimieren Codelab: Hier erfährst du, wie du den Tischmodus für Foto-Apps implementierst. Zeige den Sucher in der oberen Hälfte des Bildschirms („above the fold“ (ohne Scrollen sichtbar)) und die Steuerelemente in der unteren Hälfte („below the fold“ (mit Scrollen sichtbar)) an.
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, fängt der Buchmodus das Gefühl ein, 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 Tischmodus. 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 Konfigurationsänderung des Geräts ändern, z. B. wenn das Gerät zusammen- 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, liefern die WindowManager
WindowMetrics die Fenstergrenzen. Die API ist jedoch abwärtskompatibel
bis API-Level 14.
Weitere Informationen findest du unter Fenstergrößenklassen verwenden.
Zusätzliche Ressourcen
Beispiele
- Jetpack WindowManager: Beispiel für die Verwendung der Jetpack WindowManager-Bibliothek
- Jetcaster : Implementierung des Tischmodus mit Compose
Codelabs
- Programmieren für faltbare Smartphones und Geräte mit zwei Displays mit Jetpack WindowManager
- Optimize your camera app on foldable devices with Jetpack WindowManager