Obsługa trybów składanego wyświetlacza

Składane urządzenia zapewniają wyjątkowe wrażenia podczas oglądania. Tryb tylnego wyświetlacza i tryb podwójnego ekranu umożliwiają tworzenie specjalnych funkcji wyświetlacza na urządzeniach składanych, takich jak podgląd selfie z tylnego aparatu oraz wyświetlanie kilku różnych wyświetlaczy na ekranach wewnętrznym i zewnętrznym.

Tryb tylnego wyświetlacza

Zazwyczaj, gdy urządzenie składane jest rozłożone, aktywny jest tylko ekran wewnętrzny. Tryb tylnego wyświetlacza pozwala przenieść aktywność na ekran zewnętrzny urządzenia składanego, który podczas rozłożenia urządzenia zwykle jest skierowany w stronę użytkownika. Wewnętrzny ekran wyłączy się automatycznie.

Nowatorską aplikacją jest wyświetlanie podglądu aparatu na ekranie zewnętrznym, dzięki czemu użytkownicy mogą robić selfie tylnym aparatem, który zwykle pozwala robić lepsze zdjęcia.

Aby włączyć tryb tylnego wyświetlacza, użytkownicy muszą odpowiedzieć w oknie z prośbą o zgodę na przełączanie ekranu, na przykład:

Rysunek 1. Okno systemowe pozwalające na włączenie trybu tylnego wyświetlacza.

Okno tworzy system, więc nie musisz niczego programować. W zależności od stanu urządzenia wyświetlane są różne okna. Na przykład system każe użytkownikom otworzyć urządzenie, gdy jest zamknięte. Nie możesz dostosować tego okna, ale może się ono różnić w zależności od urządzenia od różnych dostawców OEM.

Tryb tylnego wyświetlacza możesz wypróbować w aplikacji aparatu Pixel Fold. Zobacz przykładową implementację w ćwiczeniu z programowania na stronie Korzystanie z aparatu.

Tryb Dual Screen

Tryb 2 ekranów umożliwia wyświetlanie treści na obu wyświetlaczach urządzenia składanego jednocześnie. Tryb 2 ekranu jest dostępny na Pixelu Fold z Androidem 14 (poziom interfejsu API 34) lub nowszym.

Przykładem może być korzystanie z funkcji tłumaczenia rozmowy na dwóch ekranach.

Rysunek 2. Tłumacz na 2 ekrany pokazujący różne treści na przednim i tylnym wyświetlaczu.

Automatyczne włączanie trybów

Dostęp do trybu tylnego wyświetlacza i trybu podwójnego ekranu możesz uzyskać za pomocą interfejsów API Jetpack WindowManager, począwszy od biblioteki w wersji 1.2.0-beta03.

Dodaj zależność WindowManager do pliku build.gradle modułu aplikacji:

Odlotowe

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

Kotlin

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

Punkt wejścia to WindowAreaController, który dostarcza informacje i zachowania związane z przenoszeniem okien między wyświetlaczami lub między obszarami wyświetlania na urządzeniu. WindowAreaController umożliwia tworzenie zapytań dotyczących listy dostępnych obiektów WindowAreaInfo.

Użyj WindowAreaInfo, by uzyskać dostęp do WindowAreaSession – interfejsu reprezentującego funkcję aktywnego obszaru okien. Aby określić dostępność konkretnego elementu WindowAreaCapability, użyj właściwości WindowAreaSession.

Każda możliwość jest powiązana z konkretnym elementem WindowAreaCapability.Operation. W wersjach 1.2.0-beta03 Jetpack WindowManager obsługuje dwa rodzaje operacji:

Oto przykład deklaracji zmiennych dla trybu tylnego wyświetlacza i trybu Dual Screen w głównej aktywności aplikacji:

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;

Oto jak zainicjować zmienne w metodzie onCreate() w Twojej aktywności:

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;
        }
    }
});

Zanim rozpoczniesz operację, sprawdź dostępność konkretnej funkcji:

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 currently available to be enabled.
    }
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> {
      // The selected display mode is currently available to 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 currently available to be enabled.
}
else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE)) {
  // The selected display mode is currently available to 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.    
}

Tryb Dual Screen

Ten przykład zamyka sesję, jeśli funkcja jest już aktywna lub wywołuje funkcję presentContentOnWindowArea():

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);
    }
}

Zwróć uwagę na to, że WindowAreaPresentationSessionCallback jest wykorzystywane w głównej aktywności w aplikacji.

Interfejs API wykorzystuje metodę detektora: gdy wysyłasz żądanie wyświetlenia treści na drugim wyświetlaczu urządzenia składanego, inicjujesz sesję, która jest zwracana za pomocą metody onSessionStarted() detektora. Gdy zamkniesz sesję, w metodzie onSessionEnded() pojawi się potwierdzenie.

Aby utworzyć odbiornik, zaimplementuj interfejs WindowAreaPresentationSessionCallback:

Kotlin

class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback

Java

public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback

Detektor musi wdrożyć metody onSessionStarted(), onSessionEnded(), i onContainerVisibilityChanged(). Metody wywołania zwrotnego powiadamiają o stanie sesji i umożliwiają odpowiednie zaktualizowanie aplikacji.

Wywołanie zwrotne onSessionStarted() otrzymuje WindowAreaSessionPresenter jako argument. Ten argument to kontener umożliwiający dostęp do obszaru okna i pokazywanie jego zawartości. Prezentacja może zostać automatycznie zamknięta przez system, gdy użytkownik opuści główne okno aplikacji. Prezentację można też zamknąć, wywołując metodę WindowAreaSessionPresenter#close().

W przypadku pozostałych wywołań zwrotnych dla uproszczenia wystarczy sprawdzić, czy w treści funkcji nie występują błędy, i zapisać stan:

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);
}

Aby zachować spójność w całym ekosystemie, użyj oficjalnej ikony Dual Screen, aby wskazać użytkownikom, jak włączyć lub wyłączyć tryb Dual Screen.

Praktyczny przykład znajdziesz na stronie DualScreenActivity.kt.

Tryb tylnego wyświetlacza

Podobnie jak w przypadku trybu podwójnego ekranu poniższy przykład funkcji toggleRearDisplayMode() zamyka sesję, jeśli jest już aktywna lub wywołuje funkcję transferActivityToWindowArea():

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);
    }
}

W tym przypadku wyświetlana aktywność jest wykorzystywana jako WindowAreaSessionCallback,, co jest prostsze do wdrożenia, ponieważ wywołanie zwrotne nie odbiera prezentacji, która umożliwia wyświetlenie treści w obszarze okna, ale zamiast tego przenosi całą aktywność do innego obszaru:

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}");
    }
}

Aby zachować spójność w całym ekosystemie, używaj oficjalnej ikony tylnego aparatu, aby wskazać użytkownikom, jak włączyć lub wyłączyć tryb tylnego wyświetlacza.

Dodatkowe materiały