Modi für faltbare Displays unterstützen

Faltbare Geräte bieten ein einzigartiges Wiedergabeerlebnis. Mit dem Rückdisplaymodus und dem Dual-Screen-Modus können Sie spezielle Displayfunktionen für faltbare Geräte entwickeln, z. B. eine Vorschau für Rückkamera-Selfies und gleichzeitige, aber unterschiedliche Displays auf dem inneren und äußeren Display.

Rückkameramodus

Wenn ein faltbares Gerät aufgeklappt wird, ist normalerweise nur das innere Display aktiv. Mit dem rückseitigen Displaymodus kannst du eine Aktivität auf das äußere Display eines faltbaren Smartphones verschieben Gerät, das beim Aufklappen des Geräts normalerweise vom Nutzer weg zeigt. Die wird das innere Display automatisch ausgeschaltet.

Eine neue Anwendung ist die Anzeige der Kameravorschau auf dem äußeren Display, damit Nutzer Selfies mit der Rückkamera aufnehmen können. Diese bietet in der Regel eine viel bessere Bildqualität als die Frontkamera.

Um den Modus für das Rückscheiben-Display zu aktivieren, müssen Nutzer in einem Dialogfeld der App erlauben, den Bildschirm zu wechseln, z. B.:

Abbildung 1. Systemdialogfeld zum Starten des Modus für das rückseitige Display

Der Dialog wird vom System erstellt, sodass keine Entwicklung Ihrerseits erforderlich ist. Je nach Gerätestatus werden unterschiedliche Dialogfelder angezeigt. Wenn das Gerät beispielsweise geschlossen ist, werden Nutzer aufgefordert, es zu öffnen. Sie können das Dialogfeld nicht anpassen und es kann auf Geräten verschiedener OEMs variieren.

Sie können den Modus für das Rückkameradisplay mit der Kamera App von Pixel Fold ausprobieren. Eine Beispielimplementierung finden Sie im Codelab Kamera App auf faltbaren Geräten mit Jetpack WindowManager optimieren.

Dual Screen-Modus

Mit dem Dual-Screen-Modus können Sie Inhalte auf beiden Displays eines faltbaren Geräts . Der Dual Screen-Modus ist auf Google Pixel Fold mit Android 14 (API-Level 34) oder höher verfügbar.

Ein Beispiel für einen Anwendungsfall ist der Dual Screen-Dolmetscher.

Abbildung 2: Dolmetscher mit zwei Bildschirmen, auf denen unterschiedliche Inhalte auf dem vorderen und hinteren Display angezeigt werden

Modi programmatisch aktivieren

Mit dem Jetpack-Modus können Sie auf den Rückdisplay- und den Dual-Screen-Modus zugreifen. WindowManager APIs ab Bibliotheksversion 1.2.0-beta03.

Fügen Sie der Moduldatei build.gradle Ihrer App die Abhängigkeit „WindowManager“ hinzu:

Groovy

dependencies {
    implementation "androidx.window:window:1.2.0-beta03"
}

Kotlin

dependencies {
    implementation("androidx.window:window:1.2.0-beta03")
}

Der Einstiegspunkt ist das WindowAreaController, das Informationen und das Verhalten zum Verschieben von Fenstern zwischen Displays oder zwischen Displaybereichen auf einem Gerät bereitstellt. Mit WindowAreaController können Sie die Liste der Verfügbare WindowAreaInfo-Objekte.

Mit WindowAreaInfo können Sie auf WindowAreaSession zugreifen, eine Benutzeroberfläche, die eine Funktion für den aktiven Fensterbereich darstellt. Mit WindowAreaSession kannst du die Verfügbarkeit einer bestimmten WindowAreaCapability ermitteln.

Jede Funktion bezieht sich auf eine bestimmte WindowAreaCapability.Operation. In Version 1.2.0-beta03 unterstützt Jetpack WindowManager zwei Arten von Vorgängen:

Hier ist ein Beispiel für die Deklaration von Variablen für den Modus Dual-Screen-Modus in der Hauptaktivität deiner App:

Kotlin

private lateinit var windowAreaController: WindowAreaController
private lateinit var displayExecutor: Executor
private var windowAreaSession: WindowAreaSession? = null
private var windowAreaInfo: WindowAreaInfo? = null
private var capabilityStatus: WindowAreaCapability.Status =
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED

private val dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA

Java

private WindowAreaControllerCallbackAdapter windowAreaController = null;
private Executor displayExecutor = null;
private WindowAreaSessionPresenter windowAreaSession = null;
private WindowAreaInfo windowAreaInfo = null;
private WindowAreaCapability.Status capabilityStatus  =
        WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED;

private WindowAreaCapability.Operation dualScreenOperation =
        WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA;
private WindowAreaCapability.Operation rearDisplayOperation =
        WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA;

So initialisieren Sie die Variablen in der onCreate()-Methode Ihrer Aktivität:

Kotlin

displayExecutor = ContextCompat.getMainExecutor(this)
windowAreaController = WindowAreaController.getOrCreate()

lifecycleScope.launch(Dispatchers.Main) {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        windowAreaController.windowAreaInfos
            .map { info -> info.firstOrNull { it.type == WindowAreaInfo.Type.TYPE_REAR_FACING } }
            .onEach { info -> windowAreaInfo = info }
            .map { it?.getCapability(operation)?.status ?: WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED }
            .distinctUntilChanged()
            .collect {
                capabilityStatus = it
            }
    }
}

Java

displayExecutor = ContextCompat.getMainExecutor(this);
windowAreaController = new WindowAreaControllerCallbackAdapter(WindowAreaController.getOrCreate());
windowAreaController.addWindowAreaInfoListListener(displayExecutor, this);

windowAreaController.addWindowAreaInfoListListener(displayExecutor,
  windowAreaInfos -> {
    for(WindowAreaInfo newInfo : windowAreaInfos){
        if(newInfo.getType().equals(WindowAreaInfo.Type.TYPE_REAR_FACING)){
            windowAreaInfo = newInfo;
            capabilityStatus = newInfo.getCapability(presentOperation).getStatus();
            break;
        }
    }
});

Bevor Sie einen Vorgang starten, prüfen Sie die Verfügbarkeit des jeweiligen Funktion:

Kotlin

when (capabilityStatus) {
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> {
      // The selected display mode is not supported on this device.
    }
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> {
      // The selected display mode is not available.
    }
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> {
      // The selected display mode is available and can be enabled.
    }
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> {
      // The selected display mode is already active.
    }
    else -> {
      // The selected display mode status is unknown.
    }
}

Java

if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED)) {
  // The selected display mode is not supported on this device.
}
else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE)) {
  // The selected display mode is not available.
}
else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE)) {
  // The selected display mode is available and can be enabled.
}
else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE)) {
  // The selected display mode is already active.
}
else {
  // The selected display mode status is unknown.
}

Dual Screen-Modus

Im folgenden Beispiel wird die Sitzung geschlossen, wenn die Funktion bereits aktiv ist, andernfalls wird die Funktion presentContentOnWindowArea() aufgerufen:

Kotlin

fun toggleDualScreenMode() {
    if (windowAreaSession != null) {
        windowAreaSession?.close()
    }
    else {
        windowAreaInfo?.token?.let { token ->
            windowAreaController.presentContentOnWindowArea(
                token = token,
                activity = this,
                executor = displayExecutor,
                windowAreaPresentationSessionCallback = this
            )
        }
    }
}

Java

private void toggleDualScreenMode() {
    if(windowAreaSession != null) {
        windowAreaSession.close();
    }
    else {
        Binder token = windowAreaInfo.getToken();
        windowAreaController.presentContentOnWindowArea( token, this, displayExecutor, this);
    }
}

Beachten Sie, dass die Hauptaktivität der App als Argument für WindowAreaPresentationSessionCallback verwendet wird.

Die API verwendet einen Listener-Ansatz: Wenn Sie eine Anfrage zur Darstellung des Inhalts stellen anderen Display eines faltbaren Geräts an, dann initiieren Sie eine Sitzung, mithilfe der Methode onSessionStarted() des Listeners. Wenn Sie die Sitzung erhalten Sie eine Bestätigung in der Methode onSessionEnded().

Implementiere die WindowAreaPresentationSessionCallback-Schnittstelle, um den Listener zu erstellen:

Kotlin

class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback

Java

public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback

Der Listener muss onSessionStarted() (onSessionEnded(),) implementieren. und onContainerVisibilityChanged()-Methoden. Die Callback-Methoden informieren über den Sitzungsstatus, sodass Sie die App entsprechend aktualisieren können.

Der onSessionStarted()-Callback erhält ein WindowAreaSessionPresenter als Argument. Das Argument ist der Container, mit dem Sie auf einen Fensterbereich zugreifen können und Inhalte anzuzeigen. Die Präsentation kann vom System automatisch geschlossen werden, wenn der Nutzer das primäre Anwendungsfenster verlässt. Sie kann auch durch Drücken der Tastenkombination WindowAreaSessionPresenter#close() geschlossen werden.

Bei den anderen Callbacks prüfen Sie zur Vereinfachung einfach im Funktionskörper auf Fehler und loggen Sie den Status:

Kotlin

override fun onSessionStarted(session: WindowAreaSessionPresenter) {
    windowAreaSession = session
    val view = TextView(session.context)
    view.text = "Hello world!"
    session.setContentView(view)
}

override fun onSessionEnded(t: Throwable?) {
    if(t != null) {
        Log.e(logTag, "Something was broken: ${t.message}")
    }
}

override fun onContainerVisibilityChanged(isVisible: Boolean) {
    Log.d(logTag, "onContainerVisibilityChanged. isVisible = $isVisible")
}

Java

@Override
public void onSessionStarted(@NonNull WindowAreaSessionPresenter session) {
    windowAreaSession = session;
    TextView view = new TextView(session.getContext());
    view.setText("Hello world, from the other screen!");
    session.setContentView(view);
}

@Override public void onSessionEnded(@Nullable Throwable t) {
    if(t != null) {
        Log.e(logTag, "Something was broken: ${t.message}");
    }
}

@Override public void onContainerVisibilityChanged(boolean isVisible) {
    Log.d(logTag, "onContainerVisibilityChanged. isVisible = " + isVisible);
}

Mit dem offiziellen Dual Screen-Tool können Sie für Einheitlichkeit sorgen. , um Nutzern zu zeigen, wie sie den Dual-Screen-Modus aktivieren oder deaktivieren können.

Ein funktionierendes Beispiel finden Sie unter DualScreenActivity.kt.

Rückkameramodus

Ähnlich wie im Beispiel für den Dual-Screen-Modus wird im folgenden Beispiel für eine toggleRearDisplayMode()-Funktion die Sitzung geschlossen, wenn die Funktion bereits aktiv ist, andernfalls wird die Funktion transferActivityToWindowArea() aufgerufen:

Kotlin

fun toggleRearDisplayMode() {
    if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) {
        if(windowAreaSession == null) {
            windowAreaSession = windowAreaInfo?.getActiveSession(
                operation
            )
        }
        windowAreaSession?.close()
    } else {
        windowAreaInfo?.token?.let { token ->
            windowAreaController.transferActivityToWindowArea(
                token = token,
                activity = this,
                executor = displayExecutor,
                windowAreaSessionCallback = this
            )
        }
    }
}

Java

void toggleDualScreenMode() {
    if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) {
        if(windowAreaSession == null) {
            windowAreaSession = windowAreaInfo.getActiveSession(
                operation
            )
        }
        windowAreaSession.close()
    }
    else {
        Binder token = windowAreaInfo.getToken();
        windowAreaController.transferActivityToWindowArea(token, this, displayExecutor, this);
    }
}

In diesem Fall wird die angezeigte Aktivität als WindowAreaSessionCallback verwendet, was einfacher zu implementieren ist, da der Callback keinen Vortragenden empfängt. mit der Inhalte in einem Fensterbereich angezeigt werden können, auf einen anderen Bereich übertragen:

Kotlin

override fun onSessionStarted() {
    Log.d(logTag, "onSessionStarted")
}

override fun onSessionEnded(t: Throwable?) {
    if(t != null) {
        Log.e(logTag, "Something was broken: ${t.message}")
    }
}

Java

@Override public void onSessionStarted(){
    Log.d(logTag, "onSessionStarted");
}

@Override public void onSessionEnded(@Nullable Throwable t) {
    if(t != null) {
        Log.e(logTag, "Something was broken: ${t.message}");
    }
}

Verwenden Sie zur Einheitlichkeit im gesamten System das offizielle Symbol für die Rückkamera, um Nutzern zu zeigen, wie sie den Modus für das Rückkamera-Display aktivieren oder deaktivieren.

Weitere Informationen