[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

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *