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,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 (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.
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
MediaPlayerActivity-App: Hier erfährst du, wie du Media3 Exoplayer und WindowManager verwenden kannst, um einen Videoplayer zu erstellen, der auf Faltungen reagiert.Codelab „Optimize your camera app on foldable devices with Jetpack WindowManager“: Hier erfährst du, wie du den Modus „Auf dem Tisch“ für Kamera-Apps implementierst. Zeige den Sucher in der oberen Hälfte des Bildschirms (über der Faltung) und die Steuerelemente in der unteren Hälfte (unter der Faltung) 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, 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
- Unterstützung für faltbare Smartphones und Geräte mit zwei Displays dank Jetpack WindowManager
- Kamera-App auf faltbaren Geräten mit Jetpack WindowManager optimieren