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:
- Define los recursos para cada tema de un
ResourceDictionary
. - Consuma recursos de tema en la aplicación mediante la extensión de marcado
DynamicResource
. - Establezca un tema predeterminado en el archivo App.xaml de la aplicación.
- 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:
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=" • Cynical but not unfriendly."
Style="{StaticResource SmallLabelStyle}" />
<Label Text=" • Seven varieties of grimaces."
Style="{StaticResource SmallLabelStyle}" />
<Label Text=" • 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:
- Quitar el tema actual de la aplicación. Esto se logra borrando la propiedad
MergedDictionaries
del elementoResourceDictionary
de nivel de aplicación. - Cargar el tema seleccionado. Esto se logra agregando una instancia del tema seleccionado a la propiedad
MergedDictionaries
del elementoResourceDictionary
de 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;
}
}
}