App faltbar erkennen

Das große aufgeklappte Display und der einzigartige zugeklappte Zustand ermöglichen auf faltbaren Geräten neue Möglichkeiten für Nutzer. Verwenden Sie die Jetpack WindowManager-Bibliothek, um Ihre App mit Auffaltung zu berücksichtigen. Sie bietet eine API-Oberfläche für Fensterfunktionen faltbarer Geräte, z. B. auf- und zuklappen. Wenn bei Ihrer App „Falten“ berücksichtigt wird, kann das Layout angepasst werden, damit wichtige Inhalte nicht im Bereich von Falten oder Scharnieren platziert werden. Falten und Scharniere dienen außerdem als natürliche Trennelemente.

Fensterinformationen

Die WindowInfoTracker-Oberfläche in Jetpack WindowManager zeigt Fensterlayoutinformationen an. Die Methode windowLayoutInfo() der Oberfläche gibt einen Stream von WindowLayoutInfo-Daten zurück, die Ihre App darüber informieren, dass ein faltbares Gerät zusammengeklappt ist. Mit der Methode WindowInfoTracker getOrCreate() wird eine Instanz von WindowInfoTracker erstellt.

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

Kotlin-Datenflüsse

Zum Starten und Beenden der WindowLayoutInfo-Datenerfassung können Sie eine neustartfähige, lebenszyklusbezogene Koroutine verwenden. Dabei wird der Codeblock repeatOnLifecycle ausgeführt, wenn der Lebenszyklus mindestens STARTED beträgt, 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 durch den Codeblock WindowLayoutInfo-Daten erhoben 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ätsschicht, 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 anpasst, um das Registrieren (und Aufheben der Registrierung) von Callbacks zu unterstützen und WindowLayoutInfo-Updates zu 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, die es Ihnen ermöglichen, mit Observable oder Flowable Updates zu erfassen, ohne einen Kotlin-Ablauf zu verwenden.WindowLayoutInfo

Die von den Abhängigkeiten androidx.window:window-rxjava2 und androidx.window:window-rxjava3 bereitgestellte Kompatibilitätsebene enthält die Methoden WindowInfoTracker#windowLayoutInfoFlowable() und WindowInfoTracker#windowLayoutInfoObservable(), mit denen Ihre App WindowLayoutInfo-Updates erhalten kann. 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 the WindowLayoutInfo observable
        disposable?.dispose()
   }
}

Funktionen faltbarer Displays

Mit der Klasse WindowLayoutInfo von Jetpack WindowManager werden die Funktionen eines Anzeigefensters als Liste von DisplayFeature-Elementen verfügbar gemacht.

Ein FoldingFeature ist eine Art von DisplayFeature, das unter anderem die folgenden Informationen zu faltbaren Displays bietet:

  • state: der zusammengeklappte Zustand des Geräts, FLAT oder HALF_OPENED
  • orientation: Ausrichtung der Faltung oder des Scharniers HORIZONTAL oder VERTICAL
  • occlusionType: Gibt an, ob der Umklapp oder das Scharnier einen Teil des Displays verdeckt, NONE oder FULL
  • isSeparating: Gibt an, ob durch die Faltung oder das Scharnier zwei logische Anzeigebereiche erstellt werden (wahr oder falsch)

Bei faltbaren Geräten mit HALF_OPENED wird „isSeparating“ immer als „true“ eingestuft, weil der Bildschirm in zwei Bereiche unterteilt ist. Außerdem ist „isSeparating“ auf einem Gerät mit Dual Screen immer „true“, wenn die App beide Bildschirme umfasst.

Die Eigenschaft FoldingFeature bounds (übernommen von DisplayFeature) steht für das Begrenzungsrechteck eines Faltelements, z. B. eines Faltelements oder Scharniers. Die Grenzen können verwendet werden, um Elemente auf dem Bildschirm relativ zum Element zu positionieren.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    lifecycleScope.launch(Dispatchers.Main) {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // Safely collects from windowInfoRepo 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()
                        .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 bestimmte Situationen wie den Modus „Auf dem Tisch“ unterstützen, bei dem das Smartphone auf einer Oberfläche liegt, das Scharnier in horizontaler Position steht und das faltbare Display zur Hälfte geöffnet ist.

Der Modus „Auf dem Tisch“ bietet Nutzern die Möglichkeit, ihr Smartphone zu bedienen, ohne es in der Hand zu halten. Der Modus „Auf dem Tisch“ eignet sich hervorragend zum Ansehen von Medien, zum Aufnehmen von Fotos und für Videoanrufe.

Videoplayer-App im Modus „Auf dem Tisch“

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 App-Layout entsprechend. Bei Medien-Apps bedeutet dies in der Regel, die Wiedergabe „above the fold“ zu platzieren und Steuerelemente sowie ergänzende Inhalte direkt darunter zu platzieren, um Inhalte per Sprachbefehl anzusehen oder anzuhören.

Beispiele

  • MediaPlayerActivity App: Hier erfährst du, wie du Media3 Exoplayer und WindowManager verwendest, um einen faltbaren Videoplayer zu erstellen.

  • Codelab Kamera aufklappen: Hier erfährst du, wie du den Modus „Auf dem Tisch“ in Foto-Apps implementierst. Blenden Sie 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“ ein.

Buchmodus

Eine weitere einzigartige faltbare Haltung ist der Buchmodus, in dem das Gerät zur Hälfte geöffnet und das Scharnier vertikal ist. Der Buchmodus eignet sich hervorragend zum Lesen von E-Books. Mit dem zweiseitigen Layout auf einem faltbaren großen Bildschirm, der wie ein gebundenes Buch aufgeklappt wird, erfasst der Buchmodus das Lesen eines echten Buches.

Sie kann auch für Fotos verwendet werden, wenn Sie ein anderes Seitenverhältnis aufnehmen möchten, während Sie per Sprachbefehl Fotos aufnehmen.

Implementieren Sie den Buchmodus mit denselben Techniken, die auch für den Modus „Auf dem Tisch“ verwendet werden. Der einzige Unterschied besteht darin, dass der Code prüfen 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 infolge einer Änderung der Gerätekonfiguration ändern, z. B. wenn das Gerät auf- oder zugeklappt oder gedreht wird oder die Größe eines Fensters im Mehrfenstermodus angepasst wird.

Mit der Jetpack-WindowManager-Klasse WindowMetricsCalculator können Sie die aktuellen und maximalen Fenstermesswerte abrufen. Wie bei der in API-Level 30 eingeführten Plattform WindowMetrics gibt der WindowManager WindowMetrics die Fenstergrenzen an, aber die API ist bis API-Level 14 abwärtskompatibel.

Siehe Fenstergrößenklassen.

Zusätzliche Ressourcen

Produktproben

Codelabs