App faltbar erkennen

Große Displays im aufgefalteten Zustand und einzigartige Faltzustände ermöglichen neue Nutzererfahrungen auf faltbaren Geräten. Verwende die Jetpack WindowManager-Bibliothek, um deine App mit Auffaltung zu erkennen. Sie bietet eine API-Oberfläche für Fensterfunktionen faltbarer Geräte, z. B. zum Aufklappen und Scharnieren. Wenn Ihre App faltenbewusst ist, kann sie ihr Layout anpassen, um wichtige Inhalte nicht im Bereich von Falzen oder Scharnieren zu platzieren und Falzen und Scharnieren als natürliche Trennlinien zu verwenden.

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 Benutzeroberfläche gibt einen Stream von WindowLayoutInfo-Daten zurück, der Ihre App über den Faltstatus eines faltbaren Geräts informiert. Mit der Methode WindowInfoTracker#getOrCreate() wird eine Instanz von WindowInfoTracker erstellt.

WindowManager unterstützt die Erhebung von WindowLayoutInfo-Daten mithilfe von Kotlin-Abläufen und Java-Callbacks.

Kotlin-Abläufe

Um die WindowLayoutInfo-Datenerhebung zu starten und zu beenden, können Sie eine neustartbare, lebenszyklusbewusste Coroutine verwenden, in der der repeatOnLifecycle-Codeblock ausgeführt wird, wenn der Lebenszyklus mindestens STARTED beträgt, und angehalten wird, wenn der Lebenszyklus STOPPED ist. Die Ausführung des Codeblocks wird automatisch fortgesetzt, wenn der Lebenszyklus wieder STARTED ist. Im folgenden Beispiel werden im Codeblock WindowLayoutInfo-Daten erfasst und verwendet:

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, können Sie WindowLayoutInfo-Updates erfassen, ohne einen Kotlin-Ablauf zu verwenden. Das Artefakt enthält die Klasse WindowInfoTrackerCallbackAdapter, die eine WindowInfoTracker so anpasst, dass Callbacks zum Empfangen von WindowLayoutInfo-Aktualisierungen registriert (und deregistriert) werden können, 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 Sie bereits RxJava (Version 2 oder 3) verwenden, können Sie mithilfe von Artefakten einen Observable oder Flowable verwenden, 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, umfasst die Methoden WindowInfoTracker#windowLayoutInfoFlowable() und WindowInfoTracker#windowLayoutInfoObservable(), mit denen Ihre App WindowLayoutInfo-Updates erhalten 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 von faltbaren Displays

Die WindowLayoutInfo-Klasse von Jetpack WindowManager stellt die Funktionen eines Anzeigefensters als Liste von DisplayFeature-Elementen bereit.

Eine 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: Ausrichtung der Faltung oder des Scharniers HORIZONTAL oder VERTICAL

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

  • isSeparating: Gibt an, ob durch das Scharnier zwei logische Displaybereiche entstehen, „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 auf einem Dual-Screen-Gerät immer „wahr“, wenn die App beide Bildschirme umfasst.

Die Eigenschaft FoldingFeature bounds (übernommen von DisplayFeature) stellt das Begrenzungsrechteck eines Faltelements wie Faltfunktion oder Scharnier dar. 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

Anhand der Informationen im FoldingFeature-Objekt kann Ihre App Positionen wie „Auf dem Tisch“ unterstützen, bei der das Smartphone auf einer Oberfläche liegt, das Scharnier in einer horizontalen Position ist und das faltbare Display halb geöffnet ist.

In dieser Position können Nutzer ihr Smartphone bequem bedienen, ohne es in der Hand halten zu müssen. Diese Position eignet sich hervorragend, um sich Medien anzusehen, Fotos aufzunehmen und Videoanrufe zu führen.

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

Mit FoldingFeature.State und FoldingFeature.Orientation kannst du feststellen, ob das Gerät auf „Auf dem Tisch“ gestellt wird:

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 das in der Regel, dass die Wiedergabe über dem Falz platziert und Steuerelemente und zusätzliche Inhalte direkt darunter platziert werden, damit Inhalte ohne Berühren des Displays angesehen oder angehört werden können.

Unter Android 15 (API-Level 35) und höher können Sie eine synchrone API aufrufen, um unabhängig vom aktuellen Gerätestatus festzustellen, ob ein Gerät „Auf dem 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-UI 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

Buchstatus

Eine weitere einzigartige Funktion des faltbaren Geräts ist die Buchposition, bei der das Gerät halb geöffnet ist und das Scharnier vertikal steht. Die Buchhaltung eignet sich hervorragend zum Lesen von E-Books. Mit dem zweiseitigen Layout auf einem faltbaren Großbildschirm, der wie ein gebundenes Buch aufgeklappt ist, wird das Lesen eines echten Buchs erlebt.

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

Verwenden Sie für die Buchhaltung dieselben Techniken wie für die Tischhaltung. Der einzige Unterschied besteht darin, dass im Code geprüft werden sollte, ob das Faltelement vertikal statt horizontal ausgerichtet 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-Klasse „WindowManager“ WindowMetricsCalculator können Sie die aktuellen und maximalen Fenstermesswerte abrufen. Wie die Plattform WindowMetrics, die in API-Ebene 30 eingeführt wurde, gibt auch der WindowManagerWindowMetrics die Fenstergrenzen an. Die API ist jedoch abwärtskompatibel bis API-Ebene 14.

Weitere Informationen finden Sie unter Fenstergrößenklassen verwenden.

Weitere Informationen

Produktproben

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

Codelabs