Diseños enlazables en Xamarin.Forms

Los diseños enlazables permiten que cualquier clase de diseño que derive de la clase Layout<T> genere su contenido mediante el enlace a una colección de elementos, con la opción de establecer la apariencia de cada elemento con un DataTemplate. La clase proporciona BindableLayout diseños enlazables y expone las siguientes propiedades adjuntas:

  • ItemsSource, que especifica la colección de elementos IEnumerable que va a mostrar el diseño.
  • ItemTemplate, que especifica el DataTemplate que se aplicará a cada elemento de la colección de elementos que mostrará el diseño.
  • ItemTemplateSelector, que espeficia el DataTemplateSelector que se usará para elegir un DataTemplate para un elemento en tiempo de ejecución.

Nota:

Si estableces las propiedades ItemTemplate y ItemTemplateSelector, la prioridad la tiene la propiedad ItemTemplate.

Además, la clase BindableLayout expone las siguientes propiedades enlazables:

  • EmptyView, que especifica string o la vista o que se mostrará cuando la propiedad ItemsSource sea null o cuando la colección especificada por la propiedad ItemsSource sea null o esté vacía. El valor predeterminado es null.
  • EmptyViewTemplate, que especifica que DataTemplate se mostrará cuando la propiedad ItemsSource sea null o cuando la colección especificada por la propiedad ItemsSource sea null o esté vacía. El valor predeterminado es null.

Nota:

Si estableces las propiedades EmptyView y EmptyViewTemplate, la prioridad la tiene la propiedad EmptyViewTemplate.

Todas estas propiedades se pueden adjuntar a las clases AbsoluteLayout, FlexLayout, Grid, RelativeLayout y StackLayout, que derivan de la clase Layout<T>.

La clase Layout<T> expone una colección Children a la que se agregan los elementos secundarios de un diseño. Cuando la propiedad BindableLayout.ItemsSource se establece en una colección de elementos y se adjunta a una clase derivada de Layout<T>, cada elemento de la colección se agrega a la colección Layout<T>.Children para su visualización en el diseño. Luego, la clase derivada de Layout<T> actualizará sus vistas secundarias cuando cambie la colección subyacente. Para obtener más información sobre el ciclo de diseño Xamarin.Forms, consulte Crear de un diseño personalizado.

Los diseños enlazables solo deben usarse cuando la colección de elementos que se va a mostrar es pequeña y no es necesario desplazarse ni seleccionar. Aunque el desplazamiento se puede proporcionar ajustando un diseño enlazable en ScrollView, no se recomienda porque los diseños enlazables carecen de virtualización de interfaz de usuario. Cuando se requiere el desplazamiento, se debe usar una vista desplazable que incluya virtualización de interfaz de usuario, como ListView o CollectionView. El incumplimiento de esta recomendación puede provocar problemas de rendimiento.

Importante

Aunque técnicamente es posible adjuntar un diseño enlazable a cualquier clase de diseño que derive de la clase Layout<T>, no siempre resulta práctico hacerlo, especialmente en el caso de las clases AbsoluteLayout, Grid y RelativeLayout. Por ejemplo, piensa en un escenario en el que se quiere mostrar una colección de datos en un Grid mediante un diseño enlazable, donde cada elemento de la colección es un objeto que contiene varias propiedades. Cada fila de Grid debe mostrar un objeto de la colección, y cada columna de Grid muestra una de las propiedades del objeto. Dado que el DataTemplate para el diseño enlazable solo puede contener un objeto, es necesario que ese objeto sea una clase de diseño que contenga varias vistas y que cada vista muestre una de las propiedades del objeto en una columna Grid específica. Aunque este escenario se puede cumplir con diseños enlazables, da como resultado un Grid primario que contiene un Grid secundario para cada elemento de la colección enlazada, lo que supone un uso altamente ineficaz y cuestionable del diseño Grid.

Rellenar un diseño enlazable con datos

Un diseño enlazable se rellena con datos estableciendo su propiedad ItemsSource en cualquier colección que implemente IEnumerable y adjuntándola a una clase derivada de Layout<T>:

<Grid BindableLayout.ItemsSource="{Binding Items}" />

El código de C# equivalente es el siguiente:

IEnumerable<string> items = ...;
var grid = new Grid();
BindableLayout.SetItemsSource(grid, items);

Cuando se establece la propiedad adjunta BindableLayout.ItemsSource en un diseño, pero no se establece la propiedad adjunta BindableLayout.ItemTemplate, cada elemento de la colección IEnumerable se mostrará mediante un objeto Label creado por la clase BindableLayout.

Definición de la apariencia de elementos

La apariencia de cada elemento del diseño enlazable se puede definir estableciendo la propiedad adjunta BindableLayout.ItemTemplate en un objeto DataTemplate:

<StackLayout BindableLayout.ItemsSource="{Binding User.TopFollowers}"
             Orientation="Horizontal"
             ...>
    <BindableLayout.ItemTemplate>
        <DataTemplate>
            <controls:CircleImage Source="{Binding}"
                                  Aspect="AspectFill"
                                  WidthRequest="44"
                                  HeightRequest="44"
                                  ... />
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

El código de C# equivalente es el siguiente:

DataTemplate circleImageTemplate = ...;
var stackLayout = new StackLayout();
BindableLayout.SetItemsSource(stackLayout, viewModel.User.TopFollowers);
BindableLayout.SetItemTemplate(stackLayout, circleImageTemplate);

En este ejemplo, cada elemento de la colección TopFollowers se mostrará mediante una vista CircleImage definida en el DataTemplate:

Diseño enlazable con DataTemplate

Para obtener más información sobre las plantillas de datos, consulte Plantillas de datos de Xamarin.Forms.

Elección de la apariencia del elemento en tiempo de ejecución

La apariencia de cada elemento del diseño enlazable se puede elegir en tiempo de ejecución, en función del valor del elemento, estableciendo la propiedad BindableLayout.ItemTemplateSelector adjunta en un objeto DataTemplateSelector:

<FlexLayout BindableLayout.ItemsSource="{Binding User.FavoriteTech}"
            BindableLayout.ItemTemplateSelector="{StaticResource TechItemTemplateSelector}"
            ... />

El código de C# equivalente es el siguiente:

DataTemplateSelector dataTemplateSelector = new TechItemTemplateSelector { ... };
var flexLayout = new FlexLayout();
BindableLayout.SetItemsSource(flexLayout, viewModel.User.FavoriteTech);
BindableLayout.SetItemTemplateSelector(flexLayout, dataTemplateSelector);

El DataTemplateSelector usado en la aplicación de ejemplo se muestra en el siguiente ejemplo:

public class TechItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate DefaultTemplate { get; set; }
    public DataTemplate XamarinFormsTemplate { get; set; }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        return (string)item == "Xamarin.Forms" ? XamarinFormsTemplate : DefaultTemplate;
    }
}

La clase TechItemTemplateSelector define propiedades DataTemplate DefaultTemplate y XamarinFormsTemplate que se establecen en diferentes plantillas de datos. El método OnSelectTemplate devuelve el XamarinFormsTemplate, que muestra un elemento en rojo oscuro con un corazón junto a él, cuando el elemento es igual a "Xamarin.Forms". Cuando el elemento no es igual a "Xamarin.Forms", el método OnSelectTemplate devuelve el DefaultTemplate, que muestra un elemento con el color predeterminado de un Label:

Diseño enlazable con un DataTemplateSelector

Para más información sobre los selectores de plantillas de datos, consulte Creación de una instancia de Xamarin.Forms DataTemplateSelector.

Mostrar una cadena cuando los datos no están disponibles

La propiedad EmptyView se puede establecer en una cadena, que se mostrará mediante Label cuando la propiedad ItemsSource sea null o cuando la colección especificada por la propiedad ItemsSource sea null o esté vacía. En el siguiente archivo XAML se muestra un ejemplo de este escenario:

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}"
             BindableLayout.EmptyView="No achievements">
    ...
</StackLayout>

El resultado es que, cuando la colección enlazada a datos es null, se muestra el conjunto de cadenas como valor de propiedad EmptyView:

Captura de pantalla de una vista vacía de cadena de diseño enlazable, en iOS y Android

Muestra de vistas cuando los datos no están disponibles

La propiedad EmptyView se puede establecer en una vista, que se mostrará cuando la propiedad ItemsSource sea null, o cuando la colección especificada por la propiedad ItemsSource sea null o esté vacía. Puede ser una sola vista o una vista que contenga varias vistas secundarias. En el ejemplo de XAML siguiente se muestra la propiedad EmptyView establecida en una vista que contiene varias vistas secundarias:

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
    <BindableLayout.EmptyView>
        <StackLayout>
            <Label Text="None."
                   FontAttributes="Italic"
                   FontSize="{StaticResource smallTextSize}" />
            <Label Text="Try harder and return later?"
                   FontAttributes="Italic"
                   FontSize="{StaticResource smallTextSize}" />
        </StackLayout>
    </BindableLayout.EmptyView>
    ...
</StackLayout>

El resultado es que, cuando la colección enlazada a datos es null, se muestran StackLayout y sus vistas secundarias.

Captura de pantalla de una vista vacía de diseño enlazable con varias vistas, en iOS y Android

Del mismo modo, EmptyViewTemplate se puede establecer en DataTemplate, que se mostrará cuando la propiedad ItemsSource sea null o cuando la colección especificada por la propiedad ItemsSource sea null o esté vacía. DataTemplate puede contener una sola vista o una vista que contenga varias vistas secundarias. Además, BindingContext de EmptyViewTemplate se heredará de BindingContext de BindableLayout. En el ejemplo XAML siguiente se muestra la propiedad EmptyViewTemplate establecida en DataTemplate que contiene una sola vista:

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
    <BindableLayout.EmptyViewTemplate>
        <DataTemplate>
            <Label Text="{Binding Source={x:Reference usernameLabel}, Path=Text, StringFormat='{0} has no achievements.'}" />
        </DataTemplate>
    </BindableLayout.EmptyViewTemplate>
    ...
</StackLayout>

El resultado es que cuando la colección enlazada a datos es null, se muestra Label en DataTemplate:

Captura de pantalla de una plantilla de vista vacía de diseño enlazable en iOS y Android

Nota:

La propiedad EmptyViewTemplate no se puede establecer mediante DataTemplateSelector.

Elección de emptyView en tiempo de ejecución

Las vistas que se mostrarán como EmptyView cuando los datos no estén disponibles, se pueden definir como objetos ContentView en un ResourceDictionary. Luego, la propiedad EmptyView se puede establecer en un elemento ContentView específico, en función de alguna lógica de negocios, en tiempo de ejecución. En el siguiente archivo XAML se muestra un ejemplo de este escenario:

<ContentPage ...>
    <ContentPage.Resources>
        ...    
        <ContentView x:Key="BasicEmptyView">
            <StackLayout>
                <Label Text="No achievements."
                       FontSize="14" />
            </StackLayout>
        </ContentView>
        <ContentView x:Key="AdvancedEmptyView">
            <StackLayout>
                <Label Text="None."
                       FontAttributes="Italic"
                       FontSize="14" />
                <Label Text="Try harder and return later?"
                       FontAttributes="Italic"
                       FontSize="14" />
            </StackLayout>
        </ContentView>
    </ContentPage.Resources>

    <StackLayout>
        ...
        <Switch Toggled="OnEmptyViewSwitchToggled" />

        <StackLayout x:Name="stackLayout"
                     BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
            ...
        </StackLayout>
    </StackLayout>
</ContentPage>

El XAML define dos objetos ContentView en el nivel de página ResourceDictionary, con el objeto Switch que controla qué objeto ContentView se establecerá como valor de propiedad EmptyView. Switch Cuando está activado, el controlador de eventos OnEmptyViewSwitchToggledejecuta el método ToggleEmptyView:

void ToggleEmptyView(bool isToggled)
{
    object view = isToggled ? Resources["BasicEmptyView"] : Resources["AdvancedEmptyView"];
    BindableLayout.SetEmptyView(stackLayout, view);
}

El método ToggleEmptyView establece la propiedad EmptyView del objeto stackLayout en uno de los dos objetos ContentView almacenados en ResourceDictionary, en función del valor de la propiedad Switch.IsToggled. A continuación, cuando la colección enlazada a datos es null, se muestra el objeto ContentView establecido como la propiedad EmptyView:

Captura de pantalla de la opción de vista vacía en runtime, en iOS y Android