1. Antes de comenzar
¿Qué tienen de especial los dispositivos plegables?
Los dispositivos plegables son una innovación única de esta generación. Brindan una experiencia exclusiva y, con ella, oportunidades únicas que permiten satisfacer los gustos de los usuarios con funciones como IU en el modo de mesa para usarlos con la modalidad de manos libres.
Requisitos previos
- Conocimientos básicos sobre el desarrollo de apps para Android
- Conocimientos básicos sobre el framework de inyección de dependencias Hilt
Qué compilarás
En este codelab, crearás una app de cámara con diseños optimizados para dispositivos plegables.
Comenzarás con una app de cámara básica que no responde a ninguna posición del dispositivo ni aprovecha la mejor cámara posterior para selfies mejoradas. Actualizarás el código fuente para mover la vista previa a la pantalla más pequeña cuando el dispositivo no esté desplegado y que reaccione cuando el teléfono se disponga en modo de mesa.
Aunque la aplicación de cámara es el caso de uso más conveniente para esta API, las funciones que aprenderás en este codelab se pueden aplicar a cualquier app.
Qué aprenderás
- Cómo usar Jetpack Window Manager para responder a los cambios de posición
- Cómo mover tu app a la pantalla más pequeña de un dispositivo plegable
Requisitos
- Una versión reciente de Android Studio
- Contar con un dispositivo plegable o un emulador de dispositivo plegable
2. Prepárate
Obtén el código de partida
- Si tienes Git instalado, simplemente puedes ejecutar el comando que se indica abajo. Para comprobarlo, escribe
git --version
en la terminal o línea de comandos y verifica que se ejecute de forma correcta.
git clone https://github.com/android/large-screen-codelabs.git
- Si no tienes Git, puedes hacer clic en el siguiente botón para descargar todo el código de este codelab: (opcional).
Abre el primer módulo
- En Android Studio, abre el primer módulo en
/step1
.
Si se te pide que uses la versión más reciente de Gradle, actualízala.
3. Ejecuta y observa
- Ejecuta el código del módulo
step1
.
Como puedes ver, es una app de cámara sencilla en la que puedes alternar entre la cámara frontal y la posterior y ajustar la relación de aspecto. Sin embargo, por el momento, el primer botón de la izquierda no hace nada, pero será el punto de entrada para el modo de selfie con la cámara posterior.
- Ahora, intenta colocar el dispositivo en una posición semiabierta en la cual la bisagra forme un ángulo de 90 grados.
Como puedes ver, la app no responde a las diferentes posturas del dispositivo, por lo que el diseño no cambia y deja la bisagra en medio del visor.
4. Obtén información sobre WindowManager de Jetpack
La biblioteca WindowManager de Jetpack permite que los desarrolladores de apps creen experiencias optimizadas para los dispositivos plegables. Contiene la clase FoldingFeature
que describe un pliegue en una pantalla flexible o una bisagra entre dos paneles físicos de la pantalla. Su API brinda acceso a información importante que se relaciona con el dispositivo:
state()
: MuestraFLAT
si la bisagra está abierta a 180 grados, de lo contrario, muestraHALF_OPENED
.orientation()
: MuestraFoldingFeature.Orientation.HORIZONTAL
si el ancho deFoldingFeature
es mayor que la altura, de lo contrario, muestraFoldingFeature.Orientation.VERTICAL
.bounds()
: Proporciona los límites deFoldingFeature
en formatoRect
.
La clase FoldingFeature
incluye información adicional, como occlusionType()
o isSeparating()
, pero este codelab no lo aborda en detalle.
A partir de la versión 1.1.0-beta01, la biblioteca usa WindowAreaController
, una API que permite que el modo de pantalla posterior mueva la ventana actual a la pantalla que se alinea con la cámara posterior, lo que es excelente para tomar selfies con esa cámara y para muchos otros casos de uso.
Agrega dependencias
- Para usar WindowManager de Jetpack en tu app, debes agregar las siguientes dependencias al archivo de
build.gradle
del nivel de módulo:
step1/build.gradle
def work_version = '1.1.0-beta01'
implementation "androidx.window:window:$work_version"
implementation "androidx.window:window-java:$work_version"
implementation "androidx.window:window-core:$work_version"
Ahora puedes acceder a las clases FoldingFeature
y WindowAreaController
en tu app y usarlas para crear la mejor experiencia de cámara plegable.
5. Implementa el modo de selfie con la cámara posterior
Comienza con el modo de pantalla posterior. WindowAreaControllerJavaAdapter
es la API que habilita este modo, el cual necesita un Executor
y muestra una WindowAreaSession
que guarda el estado actual. Esta WindowAreaSession
se debe retener cuando la Activity
se borra y se vuelve a crear, de modo que puedas guardarla de forma segura en el ViewModel
entre cambios de configuración.
- Declara estas variables en
MainActivity
:
step1/MainActivity.kt
private lateinit var windowAreaController: WindowAreaControllerJavaAdapter
private lateinit var displayExecutor: Executor
- Inicialízalas en el método
onCreate()
:
step1/MainActivity.kt
windowInfoTracker = WindowInfoTracker.getOrCreate(this)
displayExecutor = ContextCompat.getMainExecutor(this)
windowAreaController = WindowAreaControllerJavaAdapter(WindowAreaController.getOrCreate())
Ahora tu Activity
está lista para mover el contenido en la pantalla más pequeña, pero debes guardar la sesión.
- Para guardar la sesión, abre
CameraViewModel
y declara la siguiente variable dentro del elemento:
step1/CameraViewModel.kt
var rearDisplaySession: WindowAreaSession? = null
private set
Necesitas que rearDisplaySession
sea una variable, ya que cambia cada vez que creas una, pero quieres asegurarte de que no pueda actualizarse desde el exterior porque ahora creas un método que la actualiza cuando es necesario.
- Pega este código en
CameraViewModel
:
step1/CameraViewModel.kt
fun updateSession(newSession: WindowAreaSession? = null) {
rearDisplaySession = newSession
}
Este método se invoca cada vez que el código debe actualizar la sesión y es útil para encapsularla en un único punto de acceso.
La API de la pantalla posterior funciona con un enfoque de objeto de escucha: cuando se solicita mover el contenido a una pantalla más pequeña, se inicia una sesión que se muestra a través del método de onSessionStarted()
del objeto de escucha. Por el contrario; si quieres volver a la pantalla interior (y más grande), cierra la sesión para recibir una confirmación en el método de onSessionEnded()
. Aprovecha estos métodos para actualizar el rearDisplaySession
en el CameraViewModel
. Si quieres crear un objeto de escucha de este tipo, tienes que implementar la interfaz de WindowAreaSessionCallback
.
- Modifica la declaración
MainActivity
para implementar la interfaz deWindowAreaSessionCallback
:
step1/MainActivity.kt
class MainActivity : AppCompatActivity(), WindowAreaSessionCallback
Ahora, implementa los métodos onSessionStarted
y onSessionEnded
en la MainActivity
. En el primero, quieres guardar la WindowAreaSession
y, en el segundo, la restableces a null
. Esto resulta muy útil, ya que WindowAreaSession
te permite decidir si iniciar una sesión o cerrar una existente.
step1/MainActivity.kt
override fun onSessionEnded() {
viewModel.updateSession(null)
}
override fun onSessionStarted(session: WindowAreaSession) {
viewModel.updateSession(session)
}
- En el archivo
MainActivity.kt
, escribe el último fragmento de código necesario para que esta API funcione:
step1/MainActivity.kt
private fun startRearDisplayMode() {
if (viewModel.rearDisplaySession != null) {
viewModel.rearDisplaySession?.close()
} else {
windowAreaController.startRearDisplayModeSession(
this,
displayExecutor,
this
)
}
}
Como se mencionó anteriormente, para entender qué medidas tomar, tienes que revisar que rearDisplaySession
esté en CameraViewModel
. Si no aparece como null
, entonces la sesión ya está en curso y se cierra. Por otro lado, si aparece como null
, usa windowAreaController
para iniciar una nueva sesión y pasar Activity
dos veces. La primera vez se utiliza como Context
y la segunda como un objeto de escucha de WindowAreaSessionCallback
.
- Ahora, compila y ejecuta la app. Si luego despliegas tu teléfono y presionas el botón de la pantalla posterior, aparecerá un mensaje como el siguiente:
- Haz clic en Cambiar de pantalla ahora para ver el contenido en la pantalla externa.
6. Implementa el modo de mesa
Ahora es el momento de hacer que tu app funcione en dispositivos plegables. Para ello, mueve el contenido a un lado o por encima de la bisagra del dispositivo según la orientación del pliegue. En ese caso, actuarás dentro de FoldingStateActor
, de modo que tu código quede desvinculado de Activity
para facilitar la legibilidad.
La parte central de esta API consiste en la interfaz de WindowInfoTracker
, que se crea con un método estático que requiere Activity
:
step1/CameraCodelabDependencies.kt
@Provides
fun provideWindowInfoTracker(activity: Activity) =
WindowInfoTracker.getOrCreate(activity)
No necesitas escribir este código porque ya existe, pero es útil comprender cómo se crea WindowInfoTracker
.
- Para detectar cualquier cambio en la ventana, hazlo en el método
onResume()
de tuActivity
:
step1/MainActivity.kt
lifecycleScope.launch {
foldingStateActor.checkFoldingState(
this@MainActivity,
binding.viewFinder
)
}
- Ahora, abre el archivo
FoldingStateActor
para completar el métodocheckFoldingState()
.
Este se ejecuta en la fase RESUMED
de tu Activity
y aprovecha la WindowInfoTracker
para detectar cualquier cambio en el diseño.
step1/FoldingStateActor.kt
windowInfoTracker.windowLayoutInfo(activity)
.collect { newLayoutInfo ->
activeWindowLayoutInfo = newLayoutInfo
updateLayoutByFoldingState(cameraViewfinder)
}
Si usas la interfaz de WindowInfoTracker
, puedes llamar a windowLayoutInfo()
para recopilar un Flow
de WindowLayoutInfo
que contenga toda la información disponible en DisplayFeature
.
El último paso es reaccionar a estos cambios y mover el contenido según corresponda. Esto lo haces en el método updateLayoutByFoldingState()
, un paso a la vez.
- Asegúrate de que
activityLayoutInfo
contenga algunas propiedades deDisplayFeature
y que al menos una de ellas sea unaFoldingFeature
. De lo contrario, no realices ninguna acción:
step1/FoldingStateActor.kt
val foldingFeature = activeWindowLayoutInfo?.displayFeatures
?.firstOrNull { it is FoldingFeature } as FoldingFeature?
?: return
- Calcula la posición del pliegue para asegurarte de que la posición del dispositivo influye en el diseño y no está fuera de los límites de la jerarquía:
step1/FoldingStateActor.kt
val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
foldingFeature,
cameraViewfinder.parent as View
) ?: return
Ahora te aseguraste de que cuentas con un FoldingFeature
que influye en tu diseño, por lo que tienes que mover el contenido.
- Verifica que
FoldingFeature
esté enHALF_OPEN
o, de lo contrario, solo restablecerás la posición del contenido. Si aparece comoHALF_OPEN
, tendrás que ejecutar otra verificación y realizar otras acciones según la orientación del pliegue:
step1/FoldingStateActor.kt
if (foldingFeature.state == FoldingFeature.State.HALF_OPENED) {
when (foldingFeature.orientation) {
FoldingFeature.Orientation.VERTICAL -> {
cameraViewfinder.moveToRightOf(foldPosition)
}
FoldingFeature.Orientation.HORIZONTAL -> {
cameraViewfinder.moveToTopOf(foldPosition)
}
}
} else {
cameraViewfinder.restore()
}
Si el pliegue es VERTICAL
, mueve tu contenido a la derecha. De lo contrario, muévelo a la parte superior de la posición del pliegue.
- Compila y ejecuta tu app y, luego, despliega el teléfono y colócalo en el modo de mesa para ver cómo el contenido se mueve según el caso.
7. Felicitaciones
En este codelab, aprendiste lo particular de los dispositivos plegables, los cambios de posición y la API de pantalla posterior.