[Xamarin.Forms] Optimizando listados con estrategias de cacheo

JumpListXamarin.Forms 2.0

Con la llegada de Xamarin 4 han aterrizado una gran cantidad de diversas novedades entre las que destaca la versión 2.0 de Xamarin Forms. Entre las novedades principales contamos con:

  • Soporte a UWP.
  • Compilación de XAML (XAMLC).
  • Optimización en Layouts.
  • Optimización de listados. Incluidas nuevas estrategias de cacheo.
  • Corrección de bugs.

Optimizando listados

Entre el listado de controles fundamentales de cualquier aplicación, junto a textos, cajas de textos y botones, destacaríamos los listados. Es un control muy habitual en la inmensa mayoría de aplicaciones.

Como hemos visto en el listado de novedades de Xamarin.Forms 2.0, se han introducido mejoras en el rendimiento del control listView incluyendo nuevas estrategias de cacheo.

Estrategias de cacheo

A menudo, en listados debemos soportar grandes cantidades de elementos muy superiores a lo que se puede mostrar en pantalla e incluso a lo que se puede alcanzar con un ligero scroll. Esto impacta negativamente en el rendimiento. Tanto en iOS como en Android contamos con mecanismos de reciclado de celdas que permiten solucionar o mitigar este problema. La técnica consiste en contar solo en memoria con una pequeña cantidad de celdas en memoria de modo que se van reciclando y reutilizando elementos según el usuario realiza scroll.

Con la llegada de Xamarin.Forms 2.0 podemos aprovechar estas características nativas desde Forms. El control ListView recibe una nueva propiedad llamada CachingStrategy para esteblecer el tipo de cacheo realizado. Soporta uno de los siguientes valores:

  • RetainElement: Comportamiento por defecto. Genera una celda diferente por cada elemento del listado. Es recomendable su uso en caso de gran cantidad de cambios en el contexto. Sin embargo, hay que tener en cuenta que el código de inicialización de cada celda se ejecutará por cada celda pudiendo afectar al rendimiento.
  • RecycleElement: Esta opción toma ventajas de los mecanismos nativos de iOS y Android para el reciclado de celdas. Recomendable su uso cuando las celdas no tienen grandes cambios de contexto y su layout es similar. Reduce el consumo de memoria y la rapidez de uso.

Para realizar pruebas de rendimiento, crearemos un sencillo ejemplo con un listado con un número elevado de elementos (monos). En nuestra ViewModel creamos un listado:

[sourcecode language="vb"]
public ObservableCollection<Monkey> Monkeys
{
     get { return _monkeys; }
     set
     {
          _monkeys = value;
          RaisePropertyChanged();
     }
}
[/sourcecode]

Creamos un listado aleatorio de 1000 elementos:

[sourcecode language="vb"]
_count = 1;
Random random = new Random();
for (int i = 0; i < 1000; i++)
{
     Monkeys.Insert(0,
          new Monkey
          {
               Name = string.Format("Monkey {0}", _count),
               Location = Countries[random.Next(0, Countries.Count)],
               Photo = Images[random.Next(0, Images.Count)]
          });
     _count++;
}
[/sourcecode]

En la interfaz, utilizamos el control ListView utilizando el reciclado de celdas:

[sourcecode language="vb"]
<ListView
     ItemsSource="{Binding Monkeys}"
     CachingStrategy="RecycleElement">
     <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <StackLayout Orientation="Vertical">
            <StackLayout Orientation="Horizontal">
              <Image Source="{Binding Photo}"
                     Aspect="AspectFill"
                     WidthRequest="100"/>
              <Label Text="{Binding Name}"
                     VerticalOptions="Center"/>
              <Label Text="{Binding Location}"
                     HorizontalOptions="EndAndExpand"
                     VerticalOptions="Center"/>
            </StackLayout>
          </StackLayout>
        </ViewCell>
      </DataTemplate>
     </ListView.ItemTemplate>
</ListView>
[/sourcecode]

El resultado:

Reciclando celdas
Reciclando celdas

Tenéis el código fuente disponible en GitHub:

Ver GitHub

Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

Más información

[Xamarin.Forms] Diferentes plantillas en listado, utilizando DataTemplateSelectors

Black List-WFLlega Xamarin.Forms 2.1

Tras el lanzamiento de Xamarin.Forms 2.0 con grandes novedades como el soporte Preview de UWP o las importantes mejoras en rendimiento, todo continua su curso y tenemos ya disponible Xamarin.Forms 2.1. Contamos con las siguientes mejoras:

  • Efectos: Los efectos llegan como una nueva forma de personalizar aspecto y comportamiento de controles sin necesidad de realizar Custom Renders.
  • ControlTemplates: TemplatedPage y TemplatedView sirven ahora como clase base para ContentPage y ContentView. Se pueden usar para definir la apariencia de un control o página manteniendo una clara separación entre la jerarquía visual y el contenido.
  • DataTemplateSelectors: Poder elegir en tiempo de ejecución la plantilla a utilizar en cada elemento un listado.
  • Otros: Añadida virtualización en UWP, corrección de bugs, etc.

En este artículo vamos a centrarnos en el uso de DataTemplateSelectors en Xamarin.Forms.

DataTemplateSelectors

El objetivo de nuestro ejemplo será mostrar diferentes elementos a nivel visual en un listado. Por ejemplo, en un listado mostrando las actividades del día para una agenda, mostrar de forma visual los elementos de forma diferenciada cada tipo de actividad.

Vamos a crear un ejemplo con el ya utilizado previamente listado de monos. Algunos de ellos contarán con foto disponible mientras que otros no. Vamos a utilizar una plantilla diferente en cada caso.

Comenzamos creando en la viewmodel de la vista una colección con nuestros monos:

public ObservableCollection<Monkey> Monkeys { get; set; }

Para centrar la atención absoluta en el uso de diferentes plantillas, la carga de monos la haremos de la forma más simple posible:

private void LoadMonkeys()
{
     Monkeys = new ObservableCollection<Monkey>();

     Monkeys.Add(new Monkey
     {
          Name = "Baboon",
          Description = "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae.",
          Image = "http://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/200px-Papio_anubis_%28Serengeti%2C_2009%29.jpg"
     });

     Monkeys.Add(new Monkey
     {
          Name = "Capuchin Monkey",
          Description = "The capuchin monkeys are New World monkeys of the subfamily Cebinae. Prior to 2011, the subfamily contained only a single genus, Cebus."
     });

     Monkeys.Add(new Monkey
     {
          Name = "Blue Monkey",
          Description = "The blue monkey or diademed monkey is a species of Old World monkey native to Central and East Africa, ranging from the upper Congo River basin east to the East African Rift and south to northern Angola and Zambia"
     });

     ...
}

Creamos directamente una colección local. Como podemos ver, cada mono esta representado por nombre, descripción y foto. Sin embargo, algunos de ellos no cuentan con foto. En nuestra interfaz:

<ListView
     x:Name="list"
     ItemsSource="{Binding Monkeys}"
     CachingStrategy="RecycleElement">
</ListView>

Utilizaremos un ListView bindeado a la colección de monos.

NOTA: Desde Xamarin.Forms 2.0, el control ListView recibe una nueva propiedad llamada CachingStrategy para esteblecer el tipo de cacheo realizado.

Para la representación de elementos podemos utilizar la propiedad ItemTemplate del control junto a una plantilla. En nuestro caso, deseamos que los elementos con foto se muestren de forma diferente a cuando no contamos con foto.

Crearemos dos plantillas. Por un lado la plantilla para elementos con foto:

<DataTemplate x:Key="MonkeyTemplate">
     <ViewCell Height="200">
          <ViewCell.View>
            <Grid BackgroundColor="#d3d3d3">
              <Grid.RowDefinitions>
                <RowDefinition Height="20" />
                <RowDefinition Height="30" />
              </Grid.RowDefinitions>
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="40" />
              </Grid.ColumnDefinitions>
              <Label Grid.Row="0" Grid.Column="0"
                     Text="{Binding Name}" LineBreakMode="TailTruncation" />
              <Label Grid.Row="1" Grid.Column="0"
                     Text="{Binding Description}" Font="Small" TextColor="Gray" 
                     LineBreakMode="TailTruncation" />
              <Image Grid.Row="0" Grid.RowSpan="2" Grid.Column="1"
                     Source="{Binding Image}" Aspect="AspectFill" />
            </Grid>
          </ViewCell.View>
     </ViewCell>
</DataTemplate>

Sin foto:

<DataTemplate x:Key="NoPhotoTemplate">
     <ViewCell>
          <ViewCell.View>
            <StackLayout BackgroundColor="#d3d3d3">
              <Label Text="{Binding Name}" HorizontalOptions="End" />
              <Label Text="{Binding Description}" HorizontalOptions="Start"
                     Font="Small" TextColor="Gray" />
            </StackLayout>
          </ViewCell.View>
     </ViewCell>
</DataTemplate>

Llega el momento de añadir la lógica específica para asociar la plantilla adecuada a cada elemento en tiempo de ejecución. Crearemos una clase derivada de DataTemplateSelector:

public class MonkeyDataTemplateSelector : DataTemplateSelector

La clave la clase será el método OnSelectTemplate. Este método se lanzará por cada elemento del listado y es donde añadiremos la lógica específica para seleccionar la plantilla correspondiente. En nuestro ejemplo, crearemos dos variables de tipo DataTemplate donde tendremos cada una de las plantillas previas creadas en los recursos de la aplicación.

public class MonkeyDataTemplateSelector : DataTemplateSelector
{
     private readonly DataTemplate _monkey;
     private readonly DataTemplate _noPhoto;

     public MonkeyDataTemplateSelector()
     {
         _monkey = (DataTemplate)App.Current.Resources["MonkeyTemplate"];
         _noPhoto = (DataTemplate)App.Current.Resources["NoPhotoTemplate"];
     }

     protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
     {
         if (item is Monkey)
         {
             if(!string.IsNullOrEmpty(((Monkey)item).Image))
                 return _monkey;
         }

         return _noPhoto;
     }
}

Verificamos que el tipo de objeto bindeado en cada elemento es de tipo Monkey y verificamos si cuenta con foto o no. Dependiendo de ello, devolveremos una plantilla u otra.

En nuestra interfaz, utilizaremos el selector de plantilla en la propiedad ItemTemplate:

<ListView
     x:Name="list"
     ItemsSource="{Binding Monkeys}"
     CachingStrategy="RecycleElement">
     <ListView.ItemTemplate>
       <template:MonkeyDataTemplateSelector/>
     </ListView.ItemTemplate>
</ListView>

Todo listo!. Sencillo, ¿cierto?. Si ejecutamos la aplicación:

DataTemplateSelector en Xamarin.Forms
DataTemplateSelector en Xamarin.Forms

Podemos ver como aquellos monos que no cuentan con foto, además de no mostrarla, su nombre se encuentra alineado a la derecha.

Tenéis el código fuente disponible e GitHub:

Ver GitHub

Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

A tener en cuenta

Contamos con una serie de detalles y limitaciones a tener en cuenta:

  • En Android no podemos utilizar más de 20 plantillas por listado.
  • Un DataTemplateSelector no puede devolver otro.
  • El DataTemplateSelector no debe devolver una nueva instancia de cada DataTemplate en cada llamada, debería de usarse la misma.Cuidado con este punto si no deseamos obtener de forma sencilla Memory Leaks.

Más información

[Xamarin.Forms] Animaciones y nueva librería

AnimationIntroducción

En todas las plataformas, las aplicaciones móviles incluyen animaciones que otorgan movimiento, fluidez y focalizan la atención del usuario en las zonas deseadas. Actualmente no son un extra o añadido en las aplicaciones, sino una parte importante en la experiencia y usabilidad de las mismas.

Como desarrolladores, debemos no solo cuidar por supuesto el correcto funcionamiento de la aplicación, sino que debemos preocuparnos también por la usabilidad y la experiencia otorgada, donde entran en juego las animaciones.

En este artículo vamos a profundizar a fondo en el uso de animaciones desde Xamarin.Forms.

Animaciones en Xamarin.Forms

Xamarin.Foms incluye una serie de métodos de extensión que nos permiten realizar una completa gestión de animaciones desde código C#.

Por un lado tenemos disponibles una serie de animaciones predefinidas disponibles como métodos de extensión para elementos de tipo View. Contamos con animaciones para realizar rotaciones, escalados, tralaciones, etc. Los métodos de extensión a utilizar son:

  • FadeTo: Podemos animar la opacidad de un elemento visual.
  • RelRotateTo: Podemos especificar un ángulo de rotación para realizar transformaciones de rotación.
  • RelScaleTo: Transformación de escalado.
  • RotateTo: Transformación de rotación.
  • RotateXTo: Transformación de rotación.
  • RotateYTo: Transformación rotación.
  • ScaleTo: Transformación de escalado.
  • TranslateTo: Estableciendo las propiedades TranslationX y TranslationY podemos realizar traslaciones del elemento visual.

En caso de necesitar animaciones más complejas podemos utilizar las extensiones de animación. Contamos con varias sobrecargas del método Animate que nos permite definir animaciones más complejas.

Podemos crear un ejemplo sencillo donde probar tanto las animaciones predefinidas como las personalizadas. En la interfaz añadimos dos cuadrados (BoxView) con dos botones:

<StackLayout>
    <Label Text="Pre-defined Animations" />
    <BoxView
      x:Name="PreDefinedBox"
      HorizontalOptions="Center"
      VerticalOptions="Center"
      HeightRequest="125"
      WidthRequest="125"
      BackgroundColor="Red"/>
    <Button
      x:Name="PreDefinedButton"
      HorizontalOptions="Center"
      VerticalOptions="Center"
      Text="Animate"/>
    <Label Text="Custom Animations" />
    <BoxView
      x:Name="CustomBox"
      HorizontalOptions="Center"
      VerticalOptions="Center"
      HeightRequest="125"
      WidthRequest="125"
      BackgroundColor="Blue"/>
    <Button
      x:Name="CustomButton"
      HorizontalOptions="Center"
      VerticalOptions="Center"
      Text="Animate"/>
</StackLayout>

El primer botón animará la primera caja con animaciones predefinidas:

await PreDefinedBox.ScaleTo(2, 1000, Easing.CubicInOut);              
await PreDefinedBox.RotateTo(75, 1000, Easing.CubicInOut);                
await PreDefinedBox.ScaleTo(1, 1000, Easing.CubicInOut);

Realizamos un escalado al doble del tamaño, rotamos 75º y volvemos a escalar al tamaño original.

Sencillo, ¿verdad?

En caso de necesitar realizar animaciones personalizadas más complejas, podemos utilizar el método Animate:

CustomBox.Animate("Custom Animation", x => 
{
     CustomBox.BackgroundColor = Color.FromRgb(x, 0, 1 - x);
     CustomBox.Scale = 1 + 1.1 * x;                
}, length: 500);

Realizamos un cambio de color y escalado.

El resultado en ambos casos:

Animaciones en Xamarin.Forms
Animaciones en Xamarin.Forms

El ejemplo lo podéis encontrar en GitHub:

Ver GitHub

Xamanimation

Xamanimation es una librería destinada para Xamarin.Forms que tiene como objetivo facilitar el uso de animaciones a los desarrolladores. Añade un conjunto de animaciones de uso muy sencillo tanto desde código C# como desde código XAML.

Podemos definir animaciones en XAML a un elemento visual al cargar mediante un Behavior, usar un trigger en XAML para ejecutar la animación a voluntad al lanzar el trigger o bien desde código C#.

Animaciones disponibles

La librería cuenta una gran variedad de animaciones:

  • FadeTo
  • Flip
  • Heart
  • Jump
  • Rotate
  • Scale
  • Shake
  • Translate
  • Turnstile

Animando desde XAML

Una de las ventajas principales de la librería es la posibilidad de uso de animaciones desde XAML. Debemos utilizar el siguiente namespace:

xmlns:xamanimation="clr-namespace:Xamanimation;assembly=Xamanimation"

Al igual que en el ejemplo anterior, vamos a animar un cuadrado:

 <BoxView
     x:Name="FadeBox"
     HeightRequest="120"
     WidthRequest="120"
     Color="Blue" />

Tanto a nivel de recursos de aplicación como de vista, podemos definir animaciones:

<xamanimation:FadeToAnimation
     x:Key="FadeToAnimation"
     Target="{x:Reference FadeBox}"
     Duration="2000"
     Opacity="0"/>

Utilizando el namespace de xamanimation, tenemos acceso a todo el conjunto de animaciones disponible en la librería. En todas ellas hay una serie de parámetros comunes como:

  • Target: Nos permite indicar el elemento visual al que le aplicaremos la animación.
  • Duration: Duración de la animación en milisegundos.

Según el tipo de animación utilizada contaremos con más parámetros para personalizar la animación específica. Por ejemplo, en el caso de Fade contaremos con una propiedad Opacity para establecer como modificamos la opacidad.

Para lanzar la animación tenemos dos opciones:

  • Trigger: Llamado BeginAnimation que nos permite lanzar una animación al producirse una condición.
  • Behavior: Contamos con un Behavior llamado BeginAnimation que podemos asociar a un elemento visual de modo que que indicando la animación deseada, podemos lanzar la misma cuando se produzca la carga del elemento.

Utilizando el evento Clicked de un botón podemos lanzar la animación anterior utilizando el trigger facilitado por la librería:

<Button         
     Text="Fade">
     <Button.Triggers>
          <EventTrigger Event="Clicked">
               <xamanimation:BeginAnimation
                    Animation="{StaticResource FadeToAnimation}" />
          </EventTrigger>
     </Button.Triggers>        
</Button>

Sencillo, ¿cierto?. También contamos con el concepto de Storyboard como un conjunto de animaciones que podemos ejecutar a lo largo del tiempo:Animations 02

<xamanimation:StoryBoard
     x:Key="StoryBoard"
     Target="{x:Reference StoryBoardBox}">
       <xamanimation:ScaleToAnimation  Scale="2"/>
       <xamanimation:ShakeAnimation />
</xamanimation:StoryBoard>

El ejemplo anterior ejecutaría un escalado al doble del tamaño original y tras escalar realizaría una «agitación».

Animando desde C#

De igual forma que podemos utilizar las animaciones de la librería desde XAML, podemos hacerlo desde código C#. Contamos con un método de extensión llamado Animate que espera una instancia de cualquiera de las animaciones disponibles.

Si deseamos animar de nuevo un cuadrado llamado AnimationBox:

<BoxView
     x:Name="AnimationBox"
     HeightRequest="120"
     WidthRequest="120"
     Color="Blue" />

Bastará con acceder al elemento, utilizar el método Animate con la animación deseada:

AnimationBox.Animate(new HeartAnimation());

Podéis ver un subconjunto de las animaciones disponibles en acción a continuación:

Xamanimation
Xamanimation

El ejemplo lo podéis encontrar en GitHub:

Ver GitHub

Recordad que cualquier tipo de comentario, sugerencia o duda la podéis dejar en los comentarios de la entrada.

¿Y que esperar de la librería?

El código fuente esta disponible en GitHub y hay libertad absoluta para realizar con el lo que estiméis oportuno. Si deseáis colaborar, sin duda se aceptarán Pull Request. La librería seguirá evolucionando próximamente con:

  • Más opciones en cada animación (modo repetición)
  • Más animaciones (Color, Wobble, etc.)
  • Y otras novedades!

Más información