Diseños adaptables

1. Antes de comenzar

Los dispositivos Android están disponibles en varias formas, tamaños y factores de forma. Diseña tu app para que se ejecute en diferentes tipos de dispositivos, desde dispositivos con pantallas pequeñas hasta otros con pantallas más grandes. Si bien es posible que los desarrolladores que crean apps listas para la producción admitan Android Wear, Android Auto y Android TV, esos temas no se tratan en este curso. Si tu app admite la más amplia variedad de pantallas, podrás hacer que esté disponible para la mayor cantidad posible de usuarios con diferentes dispositivos.

Tu app debe tener un diseño flexible. En lugar de definir un diseño con dimensiones rígidas para una relación de aspecto y un tamaño de pantalla determinados, tu diseño debería poder adaptarse correctamente a varios tamaños y orientaciones de pantalla. El mismo principio se aplica cuando tu app se ejecuta en un dispositivo plegable, en el que el tamaño de la pantalla y la relación de aspecto pueden cambiar durante la ejecución. Al final de este codelab, te daremos una introducción breve a los dispositivos plegables.

aecb59fc49fb4abf.png

Requisitos previos

  • Descargar código en Android Studio y ejecutarlo
  • Conocer los componentes de la arquitectura de Android ViewModel y LiveData
  • Tener conocimientos básicos de los componentes de Navigation

Qué aprenderás

  • Cómo agregar SlidingPaneLayout a tu app

Qué compilarás

  • Actualiza la app de Deportes para adaptarla a las pantallas grandes.

Requisitos

  • Una computadora que tenga Android Studio instalado
  • Código de inicio para la app de Deportes.

Cómo descargar el código de partida para este codelab

Este codelab te brinda el código de partida para que lo extiendas con funciones que se explicaron en este codelab. El código de partida puede contener código que te resulte familiar de codelabs anteriores, así como código que no conozcas y que aprenderás en codelabs posteriores.

A fin de obtener el código de este codelab desde GitHub y abrirlo en Android Studio, haz lo siguiente:

  1. Inicia Android Studio.
  2. En la ventana Welcome to Android Studio, haz clic en Get from VCS.

61c42d01719e5b6d.png

  1. En el diálogo Get from Version Control, asegúrate de que esté seleccionado Git para Version control.

9284cfbe17219bbb.png

  1. Pega la URL del código proporcionado en el cuadro URL.
  2. Si lo prefieres, cambia el Directorio a uno diferente al predeterminado sugerido.

5ddca7dd0d914255.png

  1. Haz clic en Clonar. Android Studio comenzará a recuperar tu código.
  2. Espera a que se abra Android Studio.
  3. Selecciona el módulo correcto para tu activador de codelab, tu app o tu código de solución.

2919fe3e0c79d762.png

  1. Haz clic en el botón Run 8de56cba7583251f.png para compilar y ejecutar el código.

2. Mira el video con instrucciones para compilar (opcional)

Si quieres ver cómo uno de los instructores del curso completa el codelab, reproduce el siguiente video.

Se recomienda expandir el video a pantalla completa (con el ícono Este símbolo muestra 4 esquinas en un cuadrado destacado para indicar el modo de pantalla completa. en la esquina inferior derecha del video) para que puedas ver Android Studio y el código con mayor claridad.

Este paso es opcional. También puedes omitir el video y comenzar con las instrucciones del codelab de inmediato.

3. Descripción general de la app de inicio

La app de Deportes consta de dos pantallas. En la primera se muestra la lista de deportes. El usuario puede seleccionar un artículo de deportes en particular y se muestra la segunda pantalla. En este última, que es de detalles, aparecen noticias de los deportes seleccionados. Se muestra el texto del marcador de posición para simplificar la implementación.

Explicación del código de inicio

El código de partida que descargaste ya incluye los diseños de pantalla de la lista y pantalla de detalles listos. En esta ruta de aprendizaje, solo te enfocarás en lograr que tu app se adapte a pantallas grandes. Usarás SlidingPaneLayou para aprovechar la pantalla grande. Aquí encontrarás una breve explicación de algunos de los archivos para comenzar.

fragment_sports_list.xml

  • Abre res/layout/fragment_sports_list.xml en la vista Design.
  • Contiene el diseño de la primera pantalla de tu app que es la lista de deportes.
  • Este diseño consiste en una vista Recyclerview en la que se muestra una lista de noticias sobre deportes.

f50d3e7b41fcb338.png

d9af155f87ddbcdf.png

sports_list_item.xml

  • Abre res/layout/sports_list_item.xml en la vista Design.
  • Contiene el diseño de cada elemento de la vista Recyclerview.
  • Este diseño contiene una imagen en miniatura del deporte, el título News y el texto del marcador de posición de una noticia breve sobre deportes.

b19fd0e779c1d7c3.png

fragment_sports_news.xml

  • Abre res/layout/fragment_sports_news.xml en la vista Design.
  • Contiene el diseño de la segunda pantalla de tu app. Esta pantalla se muestra cuando el usuario selecciona un deporte desde la vista Recyclerview.
  • Este diseño contiene el banner de una imagen del deporte y el texto del marcador de posición de las noticias sobre deportes.

c2073b1752342d97.png

main_activity.xml y content_main.xml

Ambos definen el diseño de la actividad principal con un solo fragmento.

El gráfico de navegación contiene dos destinos, uno para listas de deportes y otro para noticias de deportes.

carpeta res/values

Estás familiarizado con los archivos de recursos de esta carpeta.

  • colors.xml contiene los colores del tema que se usaron en la app.
  • strings.xml contiene todas las strings que necesita tu app.
  • themes.xml contiene la personalización de la IU completada para tu app.

MainActivity.kt

Contiene el código predeterminado que genera la plantilla para establecer la vista de contenido de la actividad como main_activity.xml. Se anula el método onSupportNavigateUp() para controlar la navegación hacia arriba predeterminada desde la barra de la aplicación.

model/Sport.kt

Se trata de una clase de datos que contiene los datos que se mostrarán en cada fila de la lista de deportes Recyclerview.

data/SportsData.kt

Este archivo contiene una función llamada getSportsData(), que muestra un objeto ArrayList prepropagado con datos de deportes codificados.

SportsViewModel.kt

Este es el ViewModel compartido para la app. SportsListFragment comparte el ViewModel, la primera pantalla con la lista de deportes, y NewsDetailsFragment, la segunda pantalla con las noticias sobre deportes detalladas.

  • La propiedad _currentSport es del tipo MutableLiveData,, que almacena el deporte actual que seleccionó el usuario. La propiedad currentSport es la propiedad de copia de seguridad de _currentSport y se expone como la versión pública de solo lectura de otras clases.
  • La propiedad _sportsData contiene la lista de datos sobre deportes. Al igual que la propiedad anterior, sportsData es la versión pública de solo lectura de esta propiedad.
  • El bloque de inicializador init{} inicializa _currentSport y _sportsData. Se inicializa _sportsData con la lista completa de deportes de data/SportsData.kt. Se inicializa _currentSport con el primer elemento de la lista.
  • La función updateCurrentSport() toma una instancia de Sports y actualiza _currentSport con el valor que se pasó.

SportsAdapter.kt

Este es el adaptador para RecyclerView. En el constructor, se pasa el objeto de escucha de clics. La mayor parte del código de este archivo es código estándar que conoces de codelabs anteriores.

SportsListFragment.kt

Este es el fragmento de la primera pantalla, donde se muestra la lista de deportes.

  • La función onCreateView() aumenta el XML de diseño fragment_sports_list con el objeto de vinculación.
  • La función onViewCreated() configura el adaptador RecyclerView. Actualiza al deporte seleccionado por el usuario como el deporte actual en el ViewModel compartido, el SportsViewModel. Navega a la pantalla de detalles con las noticias sobre deportes y envía la lista de deportes al adaptador para que se muestre a través de submitList(List).

NewsDetailsFragment.kt

Esta es la segunda pantalla de tu app, en la que se muestra texto del marcador de posición de las noticias sobre deportes.

  • La función onCreateView() aumenta el XML de diseño fragment_sports_news con el objeto de vinculación.
  • La función onViewCreated() adjunta un observador en la propiedad de SportsViewModel, currentSport, para actualizar la IU automáticamente cuando cambien los datos. Dentro del observador, se actualizan el título, la imagen y las noticias de los deportes.

Compila y ejecuta la app

  1. Compila y ejecuta la app en un emulador o dispositivo. Selecciona cualquier elemento de la lista de deportes. La app debería navegar a la segunda pantalla con el texto del marcador de posición de las noticias.

4. Patrón de lista-detalles

La app de partida actual no puede aprovechar al máximo el espacio en pantalla en dispositivos más grandes, como las tablets. Para resolver este problema, debes mostrar la IU de la app usando el patrón de lista y detalles, que descubrirás en este codelab.

Cómo ejecutar la app en una tablet

En esta tarea, crearás un emulador con un perfil para tablets. Una vez que se haya creado el emulador, ejecutarás el código de partida de la app de deportes y observarás la IU.

  1. En Android Studio, ve a Tools > AVD Manager.
  2. Aparecerá la ventana Android Virtual Device Manager. Haz clic en + Create New Virtual Device... en la parte inferior.
  3. Aparecerá la ventana Virtual Device Configuration. Aquí configurarás el hardware y el SO del emulador. Haz clic en Tablet en el panel izquierdo. Selecciona Pixel C o cualquier otro perfil de hardware similar en el panel central.

8303f9b3e70321eb.png

  1. Haz clic en Next.
  2. Selecciona la imagen del sistema más reciente. Al momento de escribir este codelab, es R (nivel de API 30).
  3. Haz clic en Next.
  4. Si lo deseas, ahora puedes cambiar el nombre del dispositivo virtual.
  5. Haz clic en Finish.
  6. Se te dirigirá nuevamente a la ventana Android Virtual Device Manager. Haz clic en el ícono de inicio 38752506de85d293.png junto al dispositivo virtual recién creado.
  7. Se debería iniciar el emulador con el perfil para tablets. Ten paciencia. Este proceso puede tardar unos minutos.
  8. Cierra la ventana Android Virtual Device Manager.
  9. Ejecuta la app de deportes en el emulador que creaste recientemente.

200e209de7a2f0ad.png

Ten en cuenta que, en los dispositivos grandes, la app no utiliza toda la pantalla. El patrón de lista-detalles es más eficientes en una pantalla grande que en una lista. Un patrón de elemento y detalles, también llamado patrón principal y de detalles, muestra una lista de elementos en un lado del diseño, mientras que el detalle se muestra al lado cuando presionas un elemento. Por lo general, estas vistas solo se muestran en pantallas grandes, como las tablets, ya que tienen más espacio para mostrar más contenido.

Las siguientes imágenes son un ejemplo de un patrón de lista-detalles:

71698910dd129a91.png

Los patrones de lista-detalles anteriores muestran una lista de elementos a la izquierda y los detalles del elemento seleccionado a la derecha.

De la misma manera, si usas el patrón anterior en tu app de deportes, el fragmento de noticias será tu pantalla de detalles.

51c9542717d2f875.png

En este codelab, aprenderás a implementar la IU de lista-detalles con SlidingPaneLayout.

5. Patrón de SlidingPaneLayout

Es posible que una IU de lista-detalles deba comportarse de manera diferente según el tamaño de la pantalla. En las pantallas grandes, hay espacio suficiente para que los paneles de lista y detalles estén uno al lado del otro. Al hacer clic en un elemento de la lista, se muestran los detalles en el panel de detalles. Sin embargo, las pantallas pequeñas podrían parecer abarrotadas. En lugar de mostrar ambos paneles al mismo tiempo, es mejor mostrar uno a la vez. Inicialmente, el panel de lista ocupa toda la pantalla. Al presionar un elemento, se reemplaza el panel de lista con el panel de detalles de ese elemento, que también ocupa la pantalla.

Aprenderás a usar un SlidingPaneLayout para administrar la lógica a fin de seleccionar la experiencia del usuario adecuada según el tamaño de la pantalla actual.

b0a205de3494e95d.gif

Observa cómo el panel de detalles se desliza sobre el panel de lista en las pantallas más pequeñas.

A continuación, se muestran imágenes que ilustran cómo aparece el SlidingPaneLayout en una pantalla más pequeña. Observa cómo el panel de detalles se superpone con el panel de lista cuando se selecciona un elemento de la lista. De esta manera, ambos paneles siempre están presentes.

e26f94d9579b6121.png

471b0b38d4dfa95a.png

Por lo tanto, SlidingPaneLayout admite la visualización de dos paneles uno al lado del otro en dispositivos más grandes, pero se adapta automáticamente para mostrar solo un panel a la vez en dispositivos más pequeños, como teléfonos.

6. Cómo agregar dependencias de bibliotecas

  1. Abre build.gradle (Module: Sports.app).
  2. En la sección dependencies, incluye la siguiente dependencia para usar SlidingPaneLayout en tu app.
dependencies {
...
    implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0-beta01"
}

7. Cómo configurar un XML de fragmentos de la lista de deportes

En esta tarea, convertirás el diseño raíz de fragment_sports_list a SlidingPaneLayout. Como ya aprendiste, SlidingPaneLayout proporciona un diseño horizontal de dos paneles para usar en el nivel superior de una IU. Este diseño usa el primer panel como una lista de contenido o un navegador, subordinado a una vista principal de detalles para mostrar contenido en el otro panel.

En la app de Deportes, el primer panel será RecyclerView, que mostrará la lista de deportes, y el segundo, las noticias sobre deportes.

Agrega SlidingPaneLayout

  1. Abre fragment_sports_list.xml. Ten en cuenta que el diseño raíz es un elemento FrameLayout.
  2. Cambia FrameLayout a androidx.slidingpanelayout.widget.SlidingPaneLayout..
<androidx.slidingpanelayout.widget.SlidingPaneLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".SportsListFragment">

   <androidx.recyclerview.widget.RecyclerView...>
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
  1. Agrega un atributo android:id a SlidingPaneLayout y asígnale un valor de @+id/sliding_pane_layout.
<androidx.slidingpanelayout.widget.SlidingPaneLayout
   ...
   android:id="@+id/sliding_pane_layout"
   ...>

Agrega un segundo panel a SlidingPaneLayout

En esta tarea, agregarás un segundo elemento secundario a SlidingPaneLayout. Se mostrará como el panel de contenido correcto.

  1. En fragment_sports_list.xml, debajo de RecyclerView, agrega un segundo elemento secundario, androidx.fragment.app.FragmentContainerView.
  2. Agrega los atributos obligatorios, layout_height y layout_width, a FragmentContainerView. Asígnales un valor de match_parent. Ten en cuenta que actualizarás estos valores más tarde.
<androidx.fragment.app.FragmentContainerView
   android:layout_height="match_parent"
   android:layout_width="match_parent"/>
  1. Agrega un atributo android:id a FragmentContainerView y asígnale un valor de @+id/detail_container.
android:id="@+id/detail_container"
  1. Agrega NewsDetailsFragment a FragmentContainerView con el atributo android:name.
android:name="com.example.android.sports.NewsDetailsFragment"

Actualiza el atributo layout_width

SlidingPaneLayout usa el ancho de los dos paneles para determinar si se muestran los paneles uno al lado del otro. Por ejemplo, si el panel de la lista se mide para tener un tamaño mínimo de 300dp y el panel de detalles necesita 400dp, SlidingPaneLayout mostrará automáticamente los dos paneles uno al lado del otro, siempre que tenga disponibles al menos 700dp de ancho.

Las vistas secundarias se superponen si el ancho combinado supera el ancho disponible en el SlidingPaneLayout. En ese caso, las vistas secundarias se expanden para llenar el ancho disponible en el SlidingPaneLayout.

Para determinar el ancho de las vistas secundarias, debes contar con información básica sobre el ancho de la pantalla del dispositivo. En la siguiente tabla, se muestra la lista de puntos de interrupción determinados para que diseñes, desarrolles y pruebes con diseños redimensionables. Se eligieron específicamente a los efectos de equilibrar la simplicidad del diseño con la flexibilidad con el propósito de optimizar la app en casos únicos.

Ancho

Punto de interrupción

Representación del dispositivo

Ancho compacto

Menos de 600 dp

99.96% de los teléfonos en orientación vertical

Ancho medio

600 dp o más

El 93.73% de las tablets en portraitLarge desplegaron pantallas internas en orientación vertical

Ancho expandido

840 dp o más

El 97.22% de las tablets en landscapeLarge desplegaron pantallas internas en orientación horizontal.

a247a843310d061a.png

En la app de Deportes debes mostrar un solo panel, que es la lista de deportes en teléfonos con un ancho inferior a 600dp. Para mostrar ambos paneles en tablets, el ancho combinado debe ser mayor que 840dp. Puedes usar un ancho de 550dp para el primer elemento secundario, la vista de reciclador, y 300dp para el segundo elemento secundario, FragmentContainerView.

  1. En fragment_sports_list.xml, cambia el ancho del diseño de RecyclerView a 550dp y el de FragmentContainerView a 300dp.
<androidx.recyclerview.widget.RecyclerView
   ...
   android:layout_width="550dp"
   .../>

<androidx.fragment.app.FragmentContainerView
   ...
   android:layout_width="300dp"
   .../>
  1. Ejecuta la app en el emulador con el perfil de tablet y en el emulador con el perfil de teléfono.

ad148a96d7487e66.png

Observa que se muestran dos paneles en la tablet. En el próximo paso, corregirás el ancho del segundo panel de la tablet.

  1. Ejecuta la app en el emulador con el perfil de teléfono.

a6be6d199d2975ac.png

Agrega layout_weight

En esta tarea, corregirás la IU en la tablet y harás que el segundo panel ocupe todo el espacio restante.

SlidingPaneLayout admite la definición de cómo se divide el espacio restante después de la medición a través del parámetro de diseño layout_weight en las vistas secundarias si las vistas no se superponen. Este parámetro se aplica solo al ancho.

  1. En fragment_sports_list.xml, agrega layout_weight a FragmentContainerView y asígnale un valor de 1. Ahora, el segundo panel se expande para llenar el espacio restante después de que se mide el panel de lista.
android:layout_weight="1"
  1. Ejecuta la app.

ce3a93fe501ee5dc.png

¡Felicitaciones! Agregaste SlidingPaneLayout correctamente. Aún no has terminado. Deberás implementar la navegación hacia atrás y actualizar el segundo panel cuando se seleccione un elemento de la lista. Implementarás esto en una tarea posterior.

8. Cómo intercambiar el panel de detalles

Ejecuta la app en el emulador con el perfil de tablet. Selecciona un elemento de la lista de deportes. Observa que la app navega al panel de detalles.

8fedee8d4837909.png

En esta tarea, corregirás este problema. Actualmente, el contenido del panel dual se está actualizando con el deporte seleccionado y, luego, la app navega a NewsDetailsFragment.

  1. En el archivo SportsListFragment, en la función onViewCreated(), ubica las siguientes líneas, que navegan a la pantalla de detalles.
// Navigate to the details screen
val action = SportsListFragmentDirections.actionSportsListFragmentToNewsFragment()
this.findNavController().navigate(action)
  1. Reemplaza las líneas anteriores con el siguiente código:
binding.slidingPaneLayout.openPane()

Llama a openPane() en SlidingPaneLayout para cambiar el segundo panel sobre el primero. Esta opción no tendrá ningún efecto visible si los dos paneles están visibles, como en una tablet.

  1. Ejecuta la app en el emulador de tablets y teléfonos. Observa que el contenido del panel dual se actualiza correctamente.

b0d3c8c263be15f8.png

En tu próxima tarea, la siguiente funcionalidad que agregarás a la app es la navegación hacia atrás personalizada.

9. Cómo agregar la navegación hacia atrás personalizada

En dispositivos más pequeños, en los que los paneles de lista y detalles se superponen, debes asegurarte de que el botón Atrás del sistema lleve al usuario del panel de detalles al panel de listas. Para ello, proporciona navegación hacia atrás personalizada y conecta un OnBackPressedCallback al estado actual de SlidingPaneLayout.

Navegación hacia atrás

La navegación hacia atrás hace referencia a cómo los usuarios navegan de manera inversa por el historial de pantallas que visitaron anteriormente. Todos los dispositivos Android incluyen un botón Atrás para este tipo de navegación. Según el dispositivo Android del usuario, este botón puede ser un botón físico o de software.

Navegación hacia atrás personalizada

Android mantiene una pila de actividades de destinos a medida que el usuario navega por tu aplicación. Por lo general, esto permite que Android navegue correctamente a destinos anteriores cuando se presiona el botón Atrás. Sin embargo, hay algunos casos en los que tu app podría necesitar implementar su propio comportamiento del botón Atrás a fin de brindar una mejor experiencia del usuario.

Por ejemplo, cuando usas una WebView, como un navegador Chrome, es posible que quieras anular el comportamiento predeterminado del botón Atrás para permitir que el usuario navegue hacia atrás en su historial de navegación en lugar de las pantallas anteriores en tu app.

Del mismo modo, debes proporcionar una navegación hacia atrás personalizada a SlidingPaneLayout y navegar por la app desde el panel de detalles hasta el panel de lista.

Implementa la navegación hacia atrás personalizada

Para implementar la navegación hacia atrás personalizada en tu app de Deportes, debes hacer lo siguiente:

  • Define una devolución de llamada personalizada para controlar la pulsación de la tecla Atrás, que anula OnBackPressedCallback.
  • Registra y agrega la instancia de devolución de llamada.

Primero, define la devolución de llamada personalizada.

  1. En el archivo SportsListFragment, agrega una clase nueva debajo de la definición de la clase SportsListFragment. Asígnale el nombre SportsListOnBackPressedCallback.
  2. Pasa una instancia private de SlidingPaneLayout como parámetro del constructor.
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
)
  1. Extiende la clase desde OnBackPressedCallback. La clase OnBackPressedCallback controla las devoluciones de llamada de onBackPressed. Pronto corregirás el error de parámetro del constructor.
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback()

El constructor de OnBackPressedCallback toma un valor booleano para el estado habilitado inicial. Solo cuando se habilita una devolución de llamada (es decir, cuando el objeto isEnabled() muestra verdadero) el despachador llama al handleOnBackPressed() de la devolución de llamada para controlar el evento del botón Atrás.

  1. Pasa slidingPaneLayout.isSlideable* && slidingPaneLayout.isOpen* como parámetro del constructor a OnBackPressedCallback. El valor booleano isSlideable solo será verdadero si el segundo panel es deslizable, lo que sucedería en una pantalla más pequeña, y se muestra un solo panel. El valor de isOpen será true si el segundo panel (el panel de contenido) está completamente abierto.
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen)

Este código garantizará que la devolución de llamada solo esté habilitada en los dispositivos con pantallas más pequeñas y cuando el panel de contenido esté abierto.

  1. Para corregir el error sobre el método no implementado, haz clic en la bombilla roja 5fdf362480bfe665.png y selecciona Implement members.
  2. En la ventana emergente Implement members, haz clic en OK para anular el método handleOnBackPressed.

Tu clase debería verse así:

class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen) {
   /**
    * Callback for handling the [OnBackPressedDispatcher.onBackPressed] event.
    */
   override fun handleOnBackPressed() {
       TODO("Not yet implemented")
   }
}
  1. Dentro de la función handleOnBackPressed(), borra la declaración TODO y agrega el siguiente código para cerrar el panel de contenido y volver al panel de lista.
slidingPaneLayout.closePane()

Supervisa eventos de SlidingPaneLayout

Además de controlar los eventos de presionar la tecla Atrás, debes escuchar y supervisar los eventos relacionados con el panel deslizante. A medida que se desliza el panel de contenido, se debe habilitar o inhabilitar la devolución de llamada según corresponda. Para ello, usarás PanelSlideListener.

La interfaz SlidingPaneLayout.PanelSlideListener contiene tres métodos abstractos onPanelSlide(), onPanelOpened() y onPanelClosed(). Se llama a estos métodos cuando el panel de detalles se desliza, se abre y se cierra.

  1. Extiende la clase SportsListOnBackPressedCallback desde SlidingPaneLayout.PanelSlideListener.
  2. Para resolver el error, implementa los tres métodos. Haz clic en la bombilla roja y selecciona Implement members en Android Studio.

ad52135eecbee09f.png

  1. Tu clase SportsListOnBackPressedCallback debería ser similar a la siguiente:
class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen),
  SlidingPaneLayout.PanelSlideListener{

   override fun handleOnBackPressed() {
       slidingPaneLayout.closePane()
   }

   override fun onPanelSlide(panel: View, slideOffset: Float) {
       TODO("Not yet implemented")
   }

   override fun onPanelOpened(panel: View) {
       TODO("Not yet implemented")
   }

   override fun onPanelClosed(panel: View) {
       TODO("Not yet implemented")
   }
}
  1. Quita las sentencias TODO.
  2. Habilita la devolución de llamada OnBackPressedCallback, cuando se abra el panel de detalles (es visible). Para ello, se debe realizar una llamada a la función setEnabled() y pasar true. Escribe el siguiente código dentro de onPanelOpened():
setEnabled(true)
  1. El código anterior se puede simplificar mediante la sintaxis de acceso a la propiedad.
override fun onPanelOpened(panel: View) {
   isEnabled = true
}
  1. De manera similar, establece isEnabled en false cuando el panel de detalles esté cerrado.
override fun onPanelClosed(panel: View) {
   isEnabled = false
}
  1. El último paso para completar la devolución de llamada consiste en agregar la clase de objeto de escucha SportsListOnBackPressedCallback a la lista de objetos de escucha que recibirán notificaciones sobre los eventos de deslizamiento del panel de detalles. Agrega un bloque init a la clase SportsListOnBackPressedCallback. Dentro del bloque init, realiza una llamada a slidingPaneLayout.addPanelSlideListener() y pasa this.
init {
   slidingPaneLayout.addPanelSlideListener(this)
}

La clase SportsListOnBackPressedCallback completada debería ser similar a la siguiente:

class SportsListOnBackPressedCallback(
   private val slidingPaneLayout: SlidingPaneLayout
): OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen),
  SlidingPaneLayout.PanelSlideListener{

   init {
       slidingPaneLayout.addPanelSlideListener(this)
   }

   override fun handleOnBackPressed() {
       slidingPaneLayout.closePane()
   }

   override fun onPanelSlide(panel: View, slideOffset: Float) {
   }

   override fun onPanelOpened(panel: View) {
       isEnabled = true
   }

   override fun onPanelClosed(panel: View) {
       isEnabled = false
   }
}

Registra la devolución de llamada

Para ver la devolución de llamada en acción, regístrala con el despachador OnBackPressedDispatcher.

La clase básica de FragmentActivity te permite controlar el comportamiento del botón Atrás con su OnBackPressedDispatcher. El elemento OnBackPressedDispatcher controla cómo se despachan los eventos del botón Atrás a uno o más objetos OnBackPressedCallback.

Agrega la devolución de llamada con el método addCallback(). Este método toma un objeto LifecycleOwner. De esta manera, se garantiza que el OnBackPressedCallback solo se agregue cuando el objeto LifecycleOwner sea Lifecycle.State.STARTED. La actividad o el fragmento también quita las devoluciones de llamada registradas cuando se destruye su objeto LifecycleOwner asociado, lo que evita fugas de memoria y permite que se lo utilice en fragmentos y otros propietarios del ciclo de vida con tiempos de vida más cortos.

El método addCallback() también agrega en la instancia la clase de devolución de llamada como segundo parámetro. Deberás registrar la devolución de llamada con los siguientes pasos:

  1. En el archivo SportsListFragment, dentro de la función onViewCreated(), justo debajo de la declaración de la variable de vinculación, crea una instancia para SlidingPaneLayout y asígnale el valor de binding.slidingPaneLayout.
val slidingPaneLayout = binding.slidingPaneLayout
  1. En el archivo SportsListFragment, dentro de la función onViewCreated(), debajo de la declaración de slidingPaneLayout, agrega el siguiente código:
// Connect the SlidingPaneLayout to the system back button.
requireActivity().onBackPressedDispatcher.addCallback(
   viewLifecycleOwner,
   SportsListOnBackPressedCallback(slidingPaneLayout)
)

El código anterior usa addCallback() y pasa viewLifecycleOwner y una instancia de SportsListOnBackPressedCallback. Esta devolución de llamada solo está activa durante el ciclo de vida del fragmento.

  1. Llegó la hora de ejecutar la app en un emulador con un perfil de teléfono y ver la funcionalidad personalizada del botón Atrás en acción.

33967fa8fde5b902.gif

10. Modo bloqueado

Cuando se superponen los paneles de lista y de detalles en pantallas de menor tamaño, como los teléfonos, los usuarios pueden deslizar el dedo en ambas direcciones de forma predeterminada para alternar libremente entre los dos paneles, incluso cuando no usan la navegación por gestos. Para bloquear o desbloquear el panel de detalles, configura el modo bloqueado de SlidingPaneLayout.

  1. En el emulador con el perfil de teléfono, intenta deslizar el panel de detalles para quitarlo de la pantalla.
  2. También puedes deslizar el panel de detalles para que aparezca en la pantalla. Inténtalo tú mismo.
  3. Esta no es una función deseada en tu app de Deportes. Se recomienda bloquear el elemento SlidingPaneLayout para evitar que los usuarios deslicen el panel dentro y fuera de la pantalla con gestos. Para bloquear el elemento, en el método onViewCreated(), debajo de la definición de slidingPaneLayout, establece lockMode en LOCK_MODE_LOCKED:
slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED

Si deseas obtener más información sobre los otros modos bloqueados, consulta la documentación.

  1. Ejecuta la app una vez más y observa que el panel de detalles ahora está bloqueado.

Felicitaciones por agregar SlidingPaneLayout a tu app.

11. Código de solución

El código de la solución de este codelab se encuentra en el proyecto y módulo que se muestran a continuación.

  1. Navega a la página de repositorio de GitHub del proyecto.
  2. Verifica que el nombre de la rama coincida con el especificado en el codelab. Por ejemplo, en la siguiente captura de pantalla, el nombre de la rama es main.

1e4c0d2c081a8fd2.png

  1. En la página de GitHub de este proyecto, haz clic en el botón Code, el cual abre una ventana emergente.

1debcf330fd04c7b.png

  1. En la ventana emergente, haz clic en el botón Download ZIP para guardar el proyecto en tu computadora. Espera a que se complete la descarga.
  2. Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
  3. Haz doble clic en el archivo ZIP para descomprimirlo. Se creará una carpeta nueva con los archivos del proyecto.

Abre el proyecto en Android Studio

  1. Inicia Android Studio.
  2. En la ventana Welcome to Android Studio, haz clic en Open.

d8e9dbdeafe9038a.png

Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > Open.

8d1fda7396afe8e5.png

  1. En el navegador de archivos, ve hasta donde se encuentra la carpeta del proyecto descomprimida (probablemente en Descargas).
  2. Haz doble clic en la carpeta del proyecto.
  3. Espera a que Android Studio abra el proyecto.
  4. Haz clic en el botón Run 8de56cba7583251f.png para compilar y ejecutar la app. Asegúrate de que funcione como se espera.

12. Más información