[Xamarin UI Challenge] Flights UI

El reto

Volvemos a por un reto de interfaz de usuario con Xamarin.Forms. En esta ocasión, crear un efecto Pull To Refresh personalizado.

La UI a replicar

Los retos del ejemplo

Los retos para conseguir un Pull to Refresh personalizado son los siguientes:

  • Pull to Refresh: Contamos con el control RefreshView diseñado justamente para hacer pull to refresh. Sin embargo, el contenido al hacer refresco no es personalizable. Así que, para conseguir el resultado deseado vamos a utilizar un SwipeView. El contenido del SwipeView será el listado de vuelos mientras que usaremos un SwipeItemView (soporta una View como contenido) para añadir la animación personalizada.
  • La animación de refresco: Para la animación de refresco vamos a usar Lottie.
  • El listado de vuelos: El listado de vuelos se realizará usando CollectionView.

Listado de vuelos

El listado es de todos los elementos de la interfaz, la parte más sencilla.

<CollectionView
     ItemsSource="{Binding Booking}">
</CollectionView>

Mostramos un listado de vuelos resultados de una búsqueda. En la cabecera mostramos información básica como el número total de resultados. Podemos conseguir añadir la información encima de cada resultado usando la cabecera (Header) del CollectionView.

<CollectionView.Header>
     <Grid
          Padding="0, 12, 0, 0">
          <Label 
               Text="2 Search Results"
               Style="{StaticResource HeaderStyle}"/>
     </Grid>
</CollectionView.Header>

El resultado:

La cabecera del listado de vuelos

Cada resultado de trayecto puede tener múltiples vuelos. Vamos a usar Bindable Layout dentro de cada elemento del CollectionView.

<StackLayout
     BindableLayout.ItemsSource="{Binding Flights}"> 
</StackLayout>

De esta forma adaptaremos cada celda de forma sencilla a mostrar el total de paradas y trayectos de un resultado:

Detalles de cada vuelos

Sencillo, ¿verdad?.

Pull to Refresh personalizado

La clave del ejemplo, el efecto Pull to Refresh personalizado lo conseguimos usando el control SwipeView. SwipeView permite cuatro direcciones para hacer swipe: arriba, abajo, izquierda y derecha. En nuestro caso, queremos hacer pull desde la parte superior y por ello vamos a utilizar TopItems. Podemos usar:

  • SwipeItem: Acción contextual sencilla. Es fácil de configurar (color de fondo, texto e icono), pudiendo ejecutar un comando o evento.
  • SwipeItemView: Permite como contenido una View, por lo que las opciones de personalización son más altas.

¿Sabes que vamos a utilizar en este caso?. Probablemente has acertado, vamos a usar un SwipeItemView donde vamos a añadir nuestra animación personalizada.

<SwipeView
     x:Name="SwipeView"
     SwipeEnded="OnSwipeEnded">
     <!-- PULL TO REFRESH CONTENT -->
     <SwipeView.TopItems>
          <SwipeItemView>
               <Grid
                    HeightRequest="60">
                    <!-- CUSTOM ANIMATION -->
               </Grid>
          </SwipeItemView>
     </SwipeView.TopItems>
     <!-- CONTENT -->
     <Grid>
          <CollectionView
               ItemsSource="{Binding Booking}">
          </CollectionView>
     </Grid>
</SwipeView>

Pasamos a ver como definir la animación personalizada.

Animación usando Lottie

La librería Lottie fue creada por Airbnb inicialmente para iOS, Android y React Native. Sin embargo, gracias a la contrinución de Martijn van Dijk y otros miembros de la comuidad, tenemos soporte en Xamarin y Xamarin.Forms.

Para comenzar a trabajar con Lottie en Xamarin.Forms utilizaremos el paquete NuGet Aribnb.Xamarin.Forms.Lottie:

Install-Package Com.Airbnb.Xamarin.Forms.Lottie

Para completar el proceso de preparación de Lottie en nuestro proyecto Xamarin.Forms, necesitamos añadir la siguiente línea tras la inicialización de Xamarin.Forms en cada proyecto nativo:

AnimationViewRenderer.Init();

Para poder mostrar una animación de Lottie, debemos añadir el archivo JSON a cada proyecto nativo:

  • Android: En la carpeta Assets. El archivo debe tener como acción de compilación AndroidAsset.
  • iOS: En la carpeta Resources. El archivo debe tener como acción de compilación BundleResource.
  • UWP: En la carpeta Assets (o en la raíz), el archivo debe tener como acción de compilación Content.

Para mostrar la animación necesitamos un elemento visual que nos permita visualizar la animación además realizar una gestión de la misma. Este elemento visual es AnimationView.

<SwipeView.TopItems>
     <SwipeItemView>
          <Grid
               BackgroundColor="{StaticResource AccentColor}"
               HeightRequest="60">
               <lottie:AnimationView
                    Animation="pulltorefresh.json"
                    Loop="False" 
                    AutoPlay="True"
                    VerticalOptions="FillAndExpand"
                    HorizontalOptions="FillAndExpand"/>
          </Grid>
     </SwipeItemView>
</SwipeView.TopItems>

En nuestro ejemplo usamos esta gran animación creada por Lenny Miranda jr.

Animación de refresco

Obtener recursos

Un gran fuente de animaciones es LottieFiles. Puedes encontrar una gran variedad de animaciones listas para utilizar con opciones de búsqueda, etc.

LottieFiles

NOTA: Si usas animaciones de LottieFiles, recuerda otorgar crédito a sus creadores.

Con la definición del CollectionView, SwipeView y animación con Lottie, tenemos todo lo necesario. Veamos el resultado final!.

El resultado

Puedes encontrar el código en GitHub:

Ver GitHub

Más información

[Xamarin.Forms] Un primer vistazo a SwipeView

Opciones contextuales

Los dispositivos móviles han cambiado la forma en la que el usuario interactua con el contenido de la aplicación. Un ejemplo de ello, es el uso de opciones contextuales. Hablamos de las opciones contextuales a las que podemos acceder realizando un gesto de deslizamiento (swipe).

Opciones contextuales

En Xamarin.Forms 4.4 nos llega SwipeView, un nuevo control con el objetivo de cubrir la funcionalidad relacionada con opcones contextuales. Disponible para Android, iOS y UWP (pronto también en Tizen).

SwipeView

SwipeView permite tener una colección de elementos a los que acceder haciendo un gesto de deslizamiento (Swipe).

SwipeView

Izquierda, derecha, arriba o abajo

El uso habitual de estas opciones contextuales va asociada a un listado de elementos, pero el control SwipeView puede usarse en cualquier contexto. Podemos usar SwipeView junto con BindableLayouts, ListView, CollectionView, CarouselView o incluso sin ninguna colección.

Además, se permite hacer el swipe hacia cualquier dirección:

  • Arriba
  • Abajo
  • Izquierda
  • Derecha

Para definir la dircción del swipe, SwipeView permite definir diferentes colecciones de SwipeItems:

  • TopItems
  • BottomItems
  • LeftItems
  • RightItems

Veamos un ejemplo:

<SwipeView>
     <SwipeView.BottomItems>
          <SwipeItems
               Mode="Execute">
               <SwipeItem 
                    Text="Delete"
                    Icon="coffee.png"
                    BackgroundColor="Red"
                    Invoked="OnInvoked"/>
          </SwipeItems>
     </SwipeView.BottomItems>
     <SwipeView.Content>
          <Grid
               HeightRequest="60"
               WidthRequest="300"
               BackgroundColor="LightGray">
               <Label
                    HorizontalOptions="Center"
                    VerticalOptions="Center"
                    Text="Swipe Up (Execute)"/>
    </Grid>
    </SwipeView.Content>
</SwipeView>

Swipe en cualquier dirección

SwipeItem o SwipeItemView

La colección de SwipeItems contiene uno o N SwipeItem. Contamos con dos tipos:

SwipeItem

Es un elemento básicos que permite definir:

  • Text: El texto a mostrar debajo del icono.
  • Icon: Una propiedad de tipo ImageSource (se soportan imagenes desde recursos, URL, etc.).
  • BackgroundColor: Define el color de fondo.

SwipeItemView

Si quieres mostrar algo con más opciones de personalización, SwipeItemView permite definir una View, es decir, se puede crear el contenido de forma personalizada con una composición de elementos.

SwipeItemView es idóneo si se quiere una mayor personalización (contenido personalizado, bordes redondeados, etc.) como por ejemplo:

Usando SwipeItemView

Ambas opciones, cuentan tanto con comando (pensando en MVVM) como con eventos.

  • Command: Comando a ejecutar.
  • CommandParameter: Es el parámetro (opcional) que podemos utilizar con el comando.
  • Invoked: Además de lanzar el comando, se lanza este evento.

Diferentes modos

Se cuentan con dos modos a la hora de trabajar con SwipeItems:

  • Reveal: En el modo mostrar, se desliza para abrir un menú de uno o varios comandos y se debe pulsar explícitamente un comando para ejecutarlo.
  • Execute: En el modo ejecutar, si se suelta el elemento que se está deslizando antes de pasar un umbral, el menú se cierra y el comando no se ejecuta. Si el usuario desliza el dedo más allá del umbral y luego lo suelta, el comando se ejecuta inmediatamente.

SwipeBehaviorOnInvoked

Se puede tener control sobre lo que ocurre al pulsar un elemento gracias a la propiedad SwipeBehaviorOnInvoked. Las opciones disponibles son:

  • Auto.
  • Close: Cuando se invoque el elemento, el SwipeView siempre se cerrará y volverá al estado normal, independientemente del modo.
  • RemainOpen: Cuando se invoque el elemento, el SwipeView siempre seguirá abierto, independientemente del modo.

Ejemplo:

leftSwipeItems.SwipeBehaviorOnInvoked = SwipeBehaviorOnInvoked.Auto;

Veámoslo en funcionamiento:

SwipeBehaviorOnInvoked

Eventos

Contamos con diferentes eventos que nos permiten controlar el estado del SwipeView:

  • SwipeStarted: Se lanza justo al iniciar el deslizamiento. Ofrece la dirección del swipe como información.
  • SwipeChanging: Se lanza cada vez que el deslizamiento cambia. Además de indicar la dirección, permite obtener el offset.
  • SwipeEnded: Es similar a SwipeStarted pero este se lanza al terminar el deslizamiento.

Eventos

¿Controlar la transición?

Por defecto, se realiza una transición de revelación de cada SwipeItem. Es decir, se hace un deslizamiento del contenido mientras que el conjunto de SwipeItems permanece fijo.

¿Y si podemos elegir entre diferentes opciones?.

Podemos hacerlo gracias a un Platform Specific en Android e iOS.

leftSwipeView.On<Android>().SetSwipeTransitionMode(PlatformConfiguration.AndroidSpecific.SwipeTransitionMode.Drag);

Contamos con dos opciones:

  • Reveal
  • Drag

A continuación, puedes ver las diferencias:

Platform Specific para establecer la transición

Cerrar programáticamente el SwipeView

En ocasiones, puede ser necesario cerrar el SwipeVIew programáticamente. Esto se puede conseguir de forma sencilla utilizando el método Close:

swipeView.Close();

El resultado:

Cerrar

Un detalle importante!

SwipeView instancia y liberar recursos al abrirse o cerrarse. La ventaja de esto es que, si tenemos un listado de 1000 elementos haciendo uso de SwipeView sin tener ninguno abierto, tendremos la misma jerarquía beneficiando el rendimiento.

Llegamos al final de la introducción al SwipeView. Usaremos este control en algun GoodLooking UI sample pronto (con su correspondiente artículo!). Sin embargo, ¿qué te parece esta nueva opción?. Cualquier feedback es bienvenido en los comentarios del artículo.

Más información