App faltbar erkennen

Dank des großen aufgeklappten Displays und des aufgeklappten Zustands kannst du die Nutzung auf faltbaren Geräten noch weiter verbessern. Verwenden Sie die Bibliothek Jetpack WindowManager, um eine API-Oberfläche für Fensterfunktionen faltbarer Geräte wie das Aufklappen und das Scharnier zu erstellen, damit Ihre App faltbar ist. Wenn deine App faltbar ist, kann sie ihr Layout so anpassen, dass wichtige Inhalte nicht im Bereich der Falten oder Scharniere platziert werden, und Falten und Scharniere als natürliche Trennelemente verwendet werden.

Fensterinformationen

Über die WindowInfoTracker-Schnittstelle in Jetpack WindowManager werden Informationen zum Fensterlayout angezeigt. Die Methode windowLayoutInfo() der Benutzeroberfläche gibt einen Stream von WindowLayoutInfo-Daten zurück, die Ihre App über den faltbaren Zustand eines faltbaren Geräts informieren. Die Methode WindowInfoTracker getOrCreate() erstellt eine Instanz von WindowInfoTracker.

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

Kotlin-Datenflüsse

Wenn Sie die Datenerhebung durch WindowLayoutInfo starten und beenden möchten, können Sie eine neustartbare lebenszyklusfähige Koroutine verwenden, in der der Codeblock repeatOnLifecycle ausgeführt wird, wenn der Lebenszyklus mindestens STARTED ist, und beendet, 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 : 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 ohne Kotlin-Ablauf erfassen. Das Artefakt enthält die Klasse WindowInfoTrackerCallbackAdapter, die einen WindowInfoTracker anpasst, um das Registrieren (und Aufheben der Registrierung) von Callbacks zu unterstützen, um WindowLayoutInfo-Updates zu erhalten. 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 bereits RxJava (Version 2 oder 3) verwenden, können Sie Artefakte nutzen, die es Ihnen ermöglichen, mit Observable oder Flowable WindowLayoutInfo Updates ohne Kotlin-Ablauf zu erfassen.

Die von den androidx.window:window-rxjava2- und androidx.window:window-rxjava3-Abhängigkeiten bereitgestellte Kompatibilitätsebene umfasst 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

Die Klasse WindowLayoutInfo von Jetpack WindowManager stellt die Funktionen eines Anzeigefensters als Liste von DisplayFeature-Elementen zur Verfügung.

Ein FoldingFeature ist ein DisplayFeature-Typ, der Informationen zu faltbaren Displays liefert, darunter:

  • state: der zugeklappte Zustand des Geräts, FLAT oder HALF_OPENED
  • orientation: Ausrichtung des Fold- oder Scharniers, HORIZONTAL oder VERTICAL
  • occlusionType: Ob durch das Falt- oder Scharnier ein Teil des Displays verdeckt wird, NONE oder FULL
  • isSeparating: Gibt an, ob durch das Falten oder Scharnier zwei logische Displaybereiche entstehen – richtig oder falsch

Ein faltbares Gerät, das HALF_OPENED ist, meldet isSeparating immer als „true“, da der Bildschirm in zwei Displaybereiche 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 einer Faltfunktion, z. B. einer Faltung oder einem Scharnier. Die Begrenzungen 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“

Anhand der Informationen im FoldingFeature-Objekt kann deine App Funktionen wie den Modus „Auf dem Tisch“ unterstützen, bei dem das Smartphone auf einer Oberfläche steht, das Scharnier in horizontaler Position ist und der faltbare Bildschirm zur Hälfte geöffnet ist.

Mit dem Modus „Auf dem Tisch“ können Nutzer ihr Smartphone bequem bedienen, ohne es in der Hand zu halten. Der Modus „Auf dem Tisch“ eignet sich perfekt, um Medien anzusehen, Fotos zu machen und Videoanrufe zu starten.

Eine Videoplayer-App im Modus „Auf dem Tisch“

Mit FoldingFeature.State und FoldingFeature.Orientation können Sie 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 Sie wissen, dass sich das Gerät im Modus „Auf dem Tisch“ befindet, passen Sie das App-Layout entsprechend an. Bei Medien-Apps bedeutet das in der Regel, dass die Wiedergabe „above the fold“ (ohne Scrollen sichtbar) platziert und die Steuerelemente und ergänzenden Inhalte direkt darunter platziert werden, um sie per Sprachbefehl anzusehen oder anzuhören.

Beispiele

  • MediaPlayerActivity App: Hier erfahren Sie, wie Sie mit Media3 Exoplayer und WindowManager einen Videoplayer erstellen, der auf den Nutzer zugeschnitten ist.

  • Codelab zum Aufklappen der Kamera: Hier erfahren Sie, wie Sie den Modus „Auf dem Tisch“ für Foto-Apps implementieren. Zeigen 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“.

Buchmodus

Ein weiterer einzigartiger faltbarer Modus ist der Buchmodus, in dem das Gerät zur Hälfte aufgeklappt und das Scharnier vertikal ist. Der Buchmodus eignet sich hervorragend zum Lesen von E-Books. Dank des zweiseitigen Layouts auf einem faltbaren Bildschirm, das wie ein gebundenes Buch geöffnet ist, eignet sich der Buchmodus genau wie das Lesen eines echten Buches.

Die Kamera kann auch für Fotos verwendet werden, wenn du ein anderes Seitenverhältnis aufnehmen möchtest, während du per Sprachbefehl Fotos aufnehmen möchtest.

Implementieren Sie den Buchmodus mit denselben Techniken wie im Modus „Auf dem Tisch“. Der einzige Unterschied besteht darin, dass der Code prüfen sollte, ob die Ausrichtung des Faltelements 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 durch eine Änderung der Gerätekonfiguration ändern, wenn das Gerät beispielsweise zugeklappt oder aufgeklappt oder gedreht oder die Größe eines Fensters im Mehrfenstermodus geändert 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 stellt auch der WindowManager WindowMetrics die Fenstergrenzen bereit, aber die API ist abwärtskompatibel bis hin zum API-Level 14.

Weitere Informationen zur Unterstützung verschiedener Fenstergrößen finden Sie unter Unterstützung verschiedener Bildschirmgrößen.

Weitere Informationen

Produktproben

  • Jetpack WindowManager: Beispiel zur Verwendung der Jetpack WindowManager-Bibliothek
  • Jetcaster: Implementierung des „Auf dem Tisch“-Status mit Compose

Codelabs