Aplicación de un tema a una aplicación de Xamarin.Forms

Las aplicaciones Xamarin.Forms pueden responder a los cambios de estilo dinámicamente en tiempo de ejecución mediante la extensión de marcado DynamicResource. Esta extensión de marcado es similar a la extensión de marcado StaticResource, en que ambas usan una clave de diccionario para capturar un valor de ResourceDictionary. Pero mientras que la extensión de marcado StaticResource realiza una búsqueda de diccionario única, la extensión de marcado DynamicResource mantiene un vínculo a la clave de diccionario. Por lo tanto, si se reemplaza el valor asociado con la clave, el cambio se aplica a VisualElement. Esto permite implementar el tema en tiempo de ejecución en las aplicaciones Xamarin.Forms.

El proceso para implementar temas en tiempo de ejecución en una aplicación Xamarin.Forms es el siguiente:

  1. Define los recursos para cada tema de un ResourceDictionary.
  2. Consuma recursos de tema en la aplicación mediante la extensión de marcado DynamicResource.
  3. Establezca un tema predeterminado en el archivo App.xaml de la aplicación.
  4. Agrega código para cargar un tema en tiempo de ejecución.

Importante

Usa la extensión de marcado StaticResource si no es necesario cambiar el tema de la aplicación en tiempo de ejecución.

En las capturas de pantalla siguientes se muestran páginas con temas; la aplicación iOS usa un tema claro y la aplicación Android, uno oscuro:

Captura de pantalla de la página principal de una aplicación temática, en iOS y AndroidCaptura de pantalla de la página de detalles de una aplicación temática, en iOS y Android

Nota:

Cambiar un tema en tiempo de ejecución requiere el uso de estilos XAML y actualmente no es posible usar CSS.

Definición de temas

Un tema se define como una colección de objetos de recursos almacenados en ResourceDictionary.

En el ejemplo siguiente se muestra LightTheme desde la aplicación de ejemplo:

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="ThemingDemo.LightTheme">
    <Color x:Key="PageBackgroundColor">White</Color>
    <Color x:Key="NavigationBarColor">WhiteSmoke</Color>
    <Color x:Key="PrimaryColor">WhiteSmoke</Color>
    <Color x:Key="SecondaryColor">Black</Color>
    <Color x:Key="PrimaryTextColor">Black</Color>
    <Color x:Key="SecondaryTextColor">White</Color>
    <Color x:Key="TertiaryTextColor">Gray</Color>
    <Color x:Key="TransparentColor">Transparent</Color>
</ResourceDictionary>

En el ejemplo siguiente se muestra DarkTheme desde la aplicación de ejemplo:

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="ThemingDemo.DarkTheme">
    <Color x:Key="PageBackgroundColor">Black</Color>
    <Color x:Key="NavigationBarColor">Teal</Color>
    <Color x:Key="PrimaryColor">Teal</Color>
    <Color x:Key="SecondaryColor">White</Color>
    <Color x:Key="PrimaryTextColor">White</Color>
    <Color x:Key="SecondaryTextColor">White</Color>
    <Color x:Key="TertiaryTextColor">WhiteSmoke</Color>
    <Color x:Key="TransparentColor">Transparent</Color>
</ResourceDictionary>

Cada ResourceDictionary contiene recursos Color que definen sus respectivos temas, con cada ResourceDictionary usando valores de clave idénticos. Para más información sobre los diccionarios de recursos, consulta Diccionarios de recursos.

Importante

Se requiere un archivo de código subyacente para cada ResourceDictionary, que llama al método InitializeComponent. Esto es necesario para que se pueda crear un objeto CLR que represente el tema elegido en tiempo de ejecución.

Seleccionar un tema predeterminado

Una aplicación requiere un tema predeterminado, de modo que los controles tengan valores para los recursos que consumen. Se puede establecer un tema predeterminado mediante la combinación del elemento ResourceDictionary del tema en el elemento ResourceDictionary de nivel de aplicación definido en App.xaml:

<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ThemingDemo.App">
    <Application.Resources>
        <ResourceDictionary Source="Themes/LightTheme.xaml" />
    </Application.Resources>
</Application>

Para más información sobre los recursos, consulta Diccionarios de recursos combinados.

Consumir recursos de tema

Cuando una aplicación quiere consumir un recurso almacenado en un elemento ResourceDictionary que representa un tema, debe hacerlo con la extensión de marcado DynamicResource. Esto garantiza que si se selecciona un tema diferente en tiempo de ejecución, se aplicarán los valores del nuevo tema.

En el ejemplo siguiente se muestran tres estilos de la aplicación de ejemplo que se pueden aplicar a objetos Label:

<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ThemingDemo.App">
    <Application.Resources>

        <Style x:Key="LargeLabelStyle"
               TargetType="Label">
            <Setter Property="TextColor"
                    Value="{DynamicResource SecondaryTextColor}" />
            <Setter Property="FontSize"
                    Value="30" />
        </Style>

        <Style x:Key="MediumLabelStyle"
               TargetType="Label">
            <Setter Property="TextColor"
                    Value="{DynamicResource PrimaryTextColor}" />
            <Setter Property="FontSize"
                    Value="25" />
        </Style>

        <Style x:Key="SmallLabelStyle"
               TargetType="Label">
            <Setter Property="TextColor"
                    Value="{DynamicResource TertiaryTextColor}" />
            <Setter Property="FontSize"
                    Value="15" />
        </Style>

    </Application.Resources>
</Application>

Estos estilos se definen en el diccionario de recursos de nivel de aplicación, para que se puedan consumir en varias páginas. Cada estilo consume recursos de tema con la extensión de marcado DynamicResource.

Luego las páginas consumen estos estilos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ThemingDemo"
             x:Class="ThemingDemo.UserSummaryPage"
             Title="User Summary"
             BackgroundColor="{DynamicResource PageBackgroundColor}">
    ...
    <ScrollView>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="200" />
                <RowDefinition Height="120" />
                <RowDefinition Height="70" />
            </Grid.RowDefinitions>
            <Grid BackgroundColor="{DynamicResource PrimaryColor}">
                <Label Text="Face-Palm Monkey"
                       VerticalOptions="Center"
                       Margin="15"
                       Style="{StaticResource MediumLabelStyle}" />
                ...
            </Grid>
            <StackLayout Grid.Row="1"
                         Margin="10">
                <Label Text="This monkey reacts appropriately to ridiculous assertions and actions."
                       Style="{StaticResource SmallLabelStyle}" />
                <Label Text="  &#x2022; Cynical but not unfriendly."
                       Style="{StaticResource SmallLabelStyle}" />
                <Label Text="  &#x2022; Seven varieties of grimaces."
                       Style="{StaticResource SmallLabelStyle}" />
                <Label Text="  &#x2022; Doesn't laugh at your jokes."
                       Style="{StaticResource SmallLabelStyle}" />
            </StackLayout>
            ...
        </Grid>
    </ScrollView>
</ContentPage>

Cuando se consume directamente un recurso de tema, se debe consumir con la extensión de marcado DynamicResource. Pero cuando se consume un estilo que usa la extensión de marcado DynamicResource, se debe consumir con la extensión de marcado StaticResource.

Para obtener más información sobre los estilos, vea Aplicación de estilo a aplicaciones Xamarin.Forms con estilos XAML. Para obtener más información sobre la extensión de marcado DynamicResource, consulte Estilos dinámicos en Xamarin.Forms.

Carga de un tema en tiempo de ejecución

Cuando se selecciona un tema en tiempo de ejecución, la aplicación debe hacer lo siguiente:

  1. Quitar el tema actual de la aplicación. Esto se logra borrando la propiedad MergedDictionaries del elemento ResourceDictionary de nivel de aplicación.
  2. Cargar el tema seleccionado. Esto se logra agregando una instancia del tema seleccionado a la propiedad MergedDictionaries del elemento ResourceDictionaryde nivel de aplicación.

Los objetos VisualElement que establezcan propiedades con la extensión de marcado DynamicResource aplicarán los nuevos valores de tema. Esto ocurre porque la extensión de marcado DynamicResource mantiene un vínculo a las claves de diccionario. Por lo tanto, cuando se reemplazan los valores asociados a las claves, los cambios se aplican a los objetos VisualElement.

En la aplicación de ejemplo, se selecciona un tema a través de una página modal que contiene una clase Picker. El código siguiente muestra el método OnPickerSelectionChanged, que se ejecuta cuando cambia el tema seleccionado:

void OnPickerSelectionChanged(object sender, EventArgs e)
{
    Picker picker = sender as Picker;
    Theme theme = (Theme)picker.SelectedItem;

    ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;
    if (mergedDictionaries != null)
    {
        mergedDictionaries.Clear();

        switch (theme)
        {
            case Theme.Dark:
                mergedDictionaries.Add(new DarkTheme());
                break;
            case Theme.Light:
            default:
                mergedDictionaries.Add(new LightTheme());
                break;
        }
    }
}