Los dispositivos plegables ofrecen experiencias de visualización únicas. El modo de pantalla posterior y el modo de pantalla doble te permiten crear funciones especiales de pantalla para dispositivos plegables, como la vista previa de selfie con cámara posterior y pantallas simultáneas pero diferentes en las pantallas internas y externas.
Modo de pantalla posterior
Por lo general, cuando un dispositivo plegable está desplegado, solo está activa la pantalla interna. El modo de pantalla posterior te permite mover una actividad a la pantalla externa de un dispositivo plegable, que generalmente se orienta hacia el lado opuesto al del usuario mientras el dispositivo está desplegado. La pantalla interior se apaga automáticamente.
Una aplicación novedosa consiste en mostrar la vista previa de la cámara en la pantalla exterior para que los usuarios puedan tomar selfies con la cámara posterior, lo que generalmente proporciona un rendimiento de toma de fotos mucho mejor que la cámara frontal.
Para activar el modo de pantalla posterior, los usuarios responden a un diálogo para permitir que la app cambie de pantalla, por ejemplo:
El sistema crea el diálogo, por lo que no se requiere desarrollo de tu parte. Aparecerán diferentes diálogos según el estado del dispositivo. Por ejemplo, el sistema les indica a los usuarios que desplieguen el dispositivo si este está cerrado. No puedes personalizar el diálogo, y este puede variar según los dispositivos de diferentes OEMs.
Puedes probar el modo de pantalla posterior con la app de cámara del Pixel Fold. Consulta un ejemplo de implementación en el codelab Cómo optimizar tu app de cámara en dispositivos plegables con Jetpack WindowManager.
Modo Dual Screen
El modo Dual Screen te permite mostrar contenido en las dos pantallas de un dispositivo plegable al mismo tiempo. El modo Dual Screen está disponible en Pixel Fold con Android 14 (nivel de API 34) o versiones posteriores.
Un ejemplo de caso de uso es el intérprete de Dual Screen.
Cómo habilitar los modos de manera programática
Puedes acceder al modo de pantalla posterior y al modo de pantalla doble con las APIs de Jetpack WindowManager, a partir de la biblioteca versión 1.2.0-beta03.
Agrega la dependencia de WindowManager al archivo build.gradle
del módulo de tu app:
Groovy
dependencies { implementation "androidx.window:window:1.2.0-beta03" }
Kotlin
dependencies { implementation("androidx.window:window:1.2.0-beta03") }
El punto de entrada es WindowAreaController
, que proporciona la información y el comportamiento en relación con el movimiento de ventanas entre pantallas o áreas de visualización en un dispositivo. WindowAreaController
te permite consultar la lista de objetos WindowAreaInfo
disponibles.
Usa WindowAreaInfo
para acceder a WindowAreaSession
, una interfaz que representa una función de área de ventana activa. Usa WindowAreaSession
para determinar la disponibilidad de una WindowAreaCapability
específica.
Cada función está relacionada con una WindowAreaCapability.Operation
en particular.
En la versión 1.2.0-beta03, Jetpack WindowManager admite dos tipos de operaciones:
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
, que se usa para iniciar el modo Dual Screen.WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
, que se usa para iniciar el modo de pantalla posterior.
A continuación, se muestra un ejemplo de cómo declarar variables para el modo de pantalla posterior y el modo de pantalla doble en la actividad principal de tu 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;
A continuación, se muestra cómo inicializar las variables en el método onCreate()
de tu actividad:
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; } } });
Antes de iniciar una operación, verifica la disponibilidad de la capacidad en particular:
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. }
Modo Dual Screen
En el siguiente ejemplo, se cierra la sesión si la función ya está activa o, de lo contrario, llama a la función 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); } }
Observa el uso de la actividad principal de la app como argumento de WindowAreaPresentationSessionCallback
.
La API usa un enfoque de objeto de escucha: cuando realizas una solicitud para presentar el contenido a la otra pantalla de un dispositivo plegable, se inicia una sesión que se muestra a través del método onSessionStarted()
del objeto de escucha. Cuando cierres la sesión, recibirás una confirmación en el método onSessionEnded()
.
Para crear el objeto de escucha, implementa la interfaz WindowAreaPresentationSessionCallback
:
Kotlin
class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback
Java
public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback
El objeto de escucha debe implementar los métodos onSessionStarted()
, onSessionEnded(),
y onContainerVisibilityChanged()
. Los métodos de devolución de llamada te notifican el estado de la sesión y te permiten actualizar la app según corresponda.
La devolución de llamada onSessionStarted()
recibe un WindowAreaSessionPresenter
como argumento. El argumento es el contenedor que te permite acceder a un área de la ventana y mostrar contenido. El sistema puede descartar automáticamente la presentación cuando el usuario sale de la ventana principal de la aplicación, o bien llamar a WindowAreaSessionPresenter#close()
para cerrarla.
Para las otras devoluciones de llamada, por cuestiones de simplicidad, solo debes comprobar si hay errores en el cuerpo de la función y registrar el estado:
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); }
Para mantener la coherencia en todo el ecosistema, usa el ícono oficial de Dual Screen para indicar a los usuarios cómo habilitar o inhabilitar este modo.
Para ver una muestra funcional, consulta DualScreenActivity.kt.
Modo de pantalla posterior
Al igual que en el caso del modo de pantalla doble, el siguiente ejemplo de una función toggleRearDisplayMode()
cierra la sesión si la función ya está activa o, de lo contrario, llama a la función 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); } }
En este caso, la actividad que se muestra se usa como WindowAreaSessionCallback
, que es más fácil de implementar porque la devolución de llamada no recibe un presentador que permita mostrar contenido en un área de ventana, sino que transfiere toda la actividad a otra área:
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}"); } }
Para mantener la coherencia en todo el ecosistema, usa el ícono oficial de la cámara posterior para indicar a los usuarios cómo habilitar o inhabilitar el modo de pantalla posterior.
Recursos adicionales
- Codelab Cómo optimizar tu app de cámara en dispositivos plegables con Jetpack WindowManager
- Resumen del paquete
androidx.window.area
- Código de muestra de Jetpack WindowManager: