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 Dual Screen umożliwiają wprowadzenie specjalnych funkcji wyświetlacza dla urządzeń składanych, takich jak podgląd selfie z tylnego aparatu oraz jednoczesne, ale różne wyświetlacze na ekranach wewnętrznym i zewnętrznym.

Tryb tylnego wyświetlacza

Zazwyczaj po rozłożeniu urządzenia składanego aktywna jest tylko część ekranu wewnętrznego. Tryb tylnego wyświetlacza pozwala przenieść aktywność na zewnętrzny ekran składanego urządzenia, które jest zwykle odwrócone od użytkownika, gdy jest rozłożone. Wyświetlacz wewnętrzny wyłącza się automatycznie.

Ta nowatorska aplikacja to wyświetlanie podglądu aparatu na ekranie zewnętrznym, aby użytkownicy mogli robić selfie tylnym aparatem, co zwykle umożliwia znacznie lepsze wykonywanie zdjęć.

Aby włączyć tryb tylnego wyświetlacza, użytkownicy muszą odpowiedzieć na okno pozwalające aplikacji na przełączanie ekranów. Na przykład:

Rysunek 1. Okno systemowe umożliwiające uruchomienie trybu tylnego wyświetlacza.

To system tworzy okno dialogowe, więc nie musisz niczego programować. Okna są różne w zależności od stanu urządzenia, np. gdy jest ono zamknięte, system informuje użytkowników, że mają otworzyć urządzenie. Nie możesz dostosować tego okna, ale może się ono różnić w zależności od urządzenia danego producenta OEM.

Tryb tylnego wyświetlacza możesz wypróbować w aplikacji aparatu Pixel Fold. Zobacz przykładową implementację w ćwiczeniach z programowania Rozkładanie funkcji aparatu.

Tryb Dual Screen

Tryb Dual Screen umożliwia jednoczesne wyświetlanie treści na obu ekranach urządzenia składanego. Tryb podwójnego ekranu jest dostępny na urządzeniu Pixel Fold z Androidem 14 (poziom interfejsu API 34) lub nowszym.

Przykładem może być tłumacz na dwa ekrany.

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

Automatyczne włączanie trybów

Tryb tylnego wyświetlacza i tryb 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:

Odlotowy

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

Kotlin

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

Punktem wejścia jest WindowAreaController, który udostępnia informacje i zachowania związane z przesuwaniem okien między wyświetlaczami i między obszarami wyświetlania na urządzeniu. WindowAreaController umożliwia wysyłanie zapytań o listę dostępnych obiektów WindowAreaInfo.

Użyj WindowAreaInfo, aby uzyskać dostęp do WindowAreaSession, czyli interfejsu reprezentującego aktywną funkcję obszaru okna. Użyj metody WindowAreaSession, aby określić dostępność określonego typu WindowAreaCapability.

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

Oto przykład deklaracji zmiennych dla trybu tylnego wyświetlacza i trybu dwóch ekranów 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;

Aby zainicjować zmienne w metodzie onCreate() 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;
        }
    }
});

Przed rozpoczęciem operacji sprawdź dostępność danej 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

W tym przykładzie zamykamy sesję, jeśli możliwość jest już aktywna lub w inny sposób 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ę, że główna aktywność w aplikacji jest używana jako WindowAreaPresentationSessionCallback.

Interfejs API wykorzystuje metodę nasłuchiwania: gdy wysyłasz żądanie udostępnienia treści na inny wyświetlacz urządzenia składanego, inicjujesz sesję zwracaną przez metodę onSessionStarted() detektora. Gdy zamkniesz sesję, otrzymasz potwierdzenie w metodzie onSessionEnded().

Aby utworzyć detektor, 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 pozwalają odpowiednio zaktualizować aplikację.

Wywołanie onSessionStarted()odbiera argument WindowAreaSessionPresenter. Argumentem jest kontener, który umożliwia dostęp do obszaru okna i wyświetlanie zawartości. System może automatycznie zamknąć prezentację, gdy użytkownik opuści główne okno aplikacji. Można też ją zamknąć, wywołując metodę WindowAreaSessionPresenter#close().

W przypadku innych wywołań zwrotnych dla uproszczenia sprawdź treść funkcji pod kątem błędów i zarejestruj jej 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 poinformować użytkowników, jak włączyć lub wyłączyć ten tryb.

Praktyczny przykład znajdziesz w witrynie DualScreenActivity.kt.

Tryb tylnego wyświetlacza

Podobnie jak w przypadku trybu podwójnego, poniższy przykład funkcji toggleRearDisplayMode() zamyka sesję, jeśli możliwość jest już aktywna lub w inny sposób 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 używana jako element WindowAreaSessionCallback,, który jest łatwiejszy do zaimplementowania, ponieważ wywołanie zwrotne nie otrzymuje prezentera, który umożliwia wyświetlanie treści w obszarze okna, ale 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żyj oficjalnej ikony tylnego aparatu, aby wskazać użytkownikom, jak włączyć lub wyłączyć tryb tylnego wyświetlacza.

Dodatkowe materiały