Administrador de estado visual de Xamarin.Forms

Usa el Administrador de estado visual para realizar cambios en elementos XAML basados en estados visuales establecidos desde el código.

Visual State Manager (VSM) proporciona una manera estructurada de realizar cambios visuales en la interfaz de usuario desde el código. En la mayoría de los casos, la interfaz de usuario de la aplicación se define en XAML y este XAML incluye marcado que describe cómo afecta Visual State Manager a los objetos visuales de la interfaz de usuario.

VSM presenta el concepto de estados visuales. Una vista Xamarin.Forms, como un Button, puede tener varias apariencias visuales diferentes en función de su estado subyacente, tanto si está deshabilitada como si está presionada o tiene el foco de entrada. Ests son los estados del botón.

Los estados visuales se recopilan en grupos de estados visuales. Todos los estados visuales dentro de un grupo de estados visuales son mutuamente excluyentes. Tanto los estados visuales como los grupos de estados visuales se identifican con cadenas de texto simples.

El Administrador de estado visual de Xamarin.Forms define un grupo de estado visual denominado "CommonStates" con los siguientes estados visuales:

  • "Normal"
  • "Deshabilitado"
  • "Enfocado"
  • "Seleccionado"

Este grupo de estado visual se admite para todas las clases que derivan de VisualElement, que es la clase base para View y Page.

También puede definir sus propios grupos de estados visuales y estados visuales, como se muestra en este artículo.

Nota:

Xamarin.Forms desarrolladores familiarizados con desencadenadores son conscientes de que los desencadenadores también pueden realizar cambios en los objetos visuales de la interfaz de usuario en función de los cambios en las propiedades de una vista o en la activación de eventos. Sin embargo, el uso de desencadenadores para tratar varias combinaciones de estos cambios puede resultar bastante confuso. Históricamente, el Administrador de estado visual se introdujo en entornos basados en XAML de Windows para aliviar la confusión resultante de combinaciones de estados visuales. Con VSM, los estados visuales dentro de un grupo de estados visuales siempre son mutuamente excluyentes. En cualquier momento, solo un estado de cada grupo es el estado actual.

Pasos comunes

El Administrador de estado visual permite incluir marcado en el archivo XAML que puede cambiar la apariencia visual de una vista si la vista es normal o deshabilitada o tiene el foco de entrada. Se conocen como estados comunes.

Por ejemplo, supongamos que tienes una vista Entry en la página y deseas que la apariencia visual del objeto Entry cambie de las maneras siguientes:

  • Entry debe tener un fondo rosa cuando Entry está deshabilitado.
  • Entry debe tener un fondo de color lima normalmente.
  • Entry debe expandirse a dos veces su altura normal cuando tiene el foco de entrada.

Puede adjuntar el marcado de VSM a una vista individual o definirlo en un estilo si se aplica a varias vistas. En las dos secciones siguientes se describen estos enfoques.

Marcado de VSM en una vista

Para asociar el marcado de VSM a una vista de Entry, primero separe las etiquetas Entry de inicio y de finalización:

<Entry FontSize="18">

</Entry>

Se proporciona un tamaño de fuente explícito porque uno de los estados usará la propiedad FontSize para duplicar el tamaño del texto en Entry.

A continuación, inserte etiquetas VisualStateManager.VisualStateGroups entre estas:

<Entry FontSize="18">
    <VisualStateManager.VisualStateGroups>

    </VisualStateManager.VisualStateGroups>
</Entry>

VisualStateGroups es una propiedad enlazable adjunta definida por la clase VisualStateManager. (Para más información sobre las propiedades adjuntas, consulte el artículo Introducción a las propiedades adjuntas). Así es como se adjunta la propiedad VisualStateGroups al objeto Entry.

La propiedad VisualStateGroups es de tipo VisualStateGroupList, que es una colección de objetos VisualStateGroup. Dentro de las etiquetas VisualStateManager.VisualStateGroups, inserte un par de etiquetas VisualStateGroup para cada grupo de estados visuales que desee incluir:

<Entry FontSize="18">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CommonStates">

        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Entry>

Observe que la etiqueta VisualStateGroup tiene un atributo x:Name que indica el nombre del grupo. La clase VisualStateGroup define una propiedad Name que puede usar en su lugar:

<VisualStateGroup Name="CommonStates">

Puede usar x:Name o Name, pero no ambos en el mismo elemento.

La clase VisualStateGroup también hereda una propiedad denominada States, que es una colección de objetos VisualState. States es la propiedad de contenido de VisualStateGroups para que pueda incluir las etiquetas VisualState directamente entre las etiquetas VisualStateGroup. (Las propiedades de contenido se describen en el artículo Sintaxis XAML esencial).

El siguiente paso consiste en incluir un par de etiquetas para cada estado visual de ese grupo. También se pueden identificar mediante x:Name o Name:

<Entry FontSize="18">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CommonStates">
            <VisualState x:Name="Normal">

            </VisualState>

            <VisualState x:Name="Focused">

            </VisualState>

            <VisualState x:Name="Disabled">

            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Entry>

VisualState define una propiedad denominada Setters, que es una colección de objetos Setter. Son los mismos objetos Setter que se usan en un objeto Style.

Setters no es la propiedad de contenido de VisualState, por lo que es necesario incluir etiquetas de elemento de propiedad para la propiedad Setters:

<Entry FontSize="18">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CommonStates">
            <VisualState x:Name="Normal">
                <VisualState.Setters>

                </VisualState.Setters>
            </VisualState>

            <VisualState x:Name="Focused">
                <VisualState.Setters>

                </VisualState.Setters>
            </VisualState>

            <VisualState x:Name="Disabled">
                <VisualState.Setters>

                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Entry>

Ahora puede insertar uno o varios objetos Setter entre cada par de etiquetas Setters. Estos son los objetos Setter que definen los estados visuales descritos anteriormente:

<Entry FontSize="18">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CommonStates">
            <VisualState x:Name="Normal">
                <VisualState.Setters>
                    <Setter Property="BackgroundColor" Value="Lime" />
                </VisualState.Setters>
            </VisualState>

            <VisualState x:Name="Focused">
                <VisualState.Setters>
                    <Setter Property="FontSize" Value="36" />
                </VisualState.Setters>
            </VisualState>

            <VisualState x:Name="Disabled">
                <VisualState.Setters>
                    <Setter Property="BackgroundColor" Value="Pink" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Entry>

Cada etiqueta Setter indica el valor de una propiedad determinada cuando ese estado es actual. Cualquier propiedad a la que hace referencia un objeto Setter debe estar respaldada por una propiedad enlazable.

Un marcado similar a este es la base de la página VSM en vista del programa de ejemplo. La página incluye tres vistas Entry, pero solo la segunda tiene el marcado VSM asociado:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:VsmDemos"
             x:Class="VsmDemos.MainPage"
             Title="VSM Demos">

    <StackLayout>
        <StackLayout.Resources>
            <Style TargetType="Entry">
                <Setter Property="Margin" Value="20, 0" />
                <Setter Property="FontSize" Value="18" />
            </Style>

            <Style TargetType="Label">
                <Setter Property="Margin" Value="20, 30, 20, 0" />
                <Setter Property="FontSize" Value="Large" />
            </Style>
        </StackLayout.Resources>

        <Label Text="Normal Entry:" />
        <Entry />
        <Label Text="Entry with VSM: " />
        <Entry>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="CommonStates">
                    <VisualState x:Name="Normal">
                        <VisualState.Setters>
                            <Setter Property="BackgroundColor" Value="Lime" />
                        </VisualState.Setters>
                    </VisualState>
                    <VisualState x:Name="Focused">
                        <VisualState.Setters>
                            <Setter Property="FontSize" Value="36" />
                        </VisualState.Setters>
                    </VisualState>
                    <VisualState x:Name="Disabled">
                        <VisualState.Setters>
                            <Setter Property="BackgroundColor" Value="Pink" />
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>

            <Entry.Triggers>
                <DataTrigger TargetType="Entry"
                             Binding="{Binding Source={x:Reference entry3},
                                               Path=Text.Length}"
                             Value="0">
                    <Setter Property="IsEnabled" Value="False" />
                </DataTrigger>
            </Entry.Triggers>
        </Entry>
        <Label Text="Entry to enable 2nd Entry:" />
        <Entry x:Name="entry3"
               Text=""
               Placeholder="Type something to enable 2nd Entry" />
    </StackLayout>
</ContentPage>

Observe que el segundo Entry también tiene como DataTrigger parte de su colección Trigger. Esto hace que Entry se deshabilite hasta que se escriba algo en el tercer Entry. Esta es la página en el inicio que se ejecuta en iOS, Android y la Plataforma universal de Windows (UWP):

VSM en vista: Deshabilitado

El estado visual actual es "Deshabilitado", por lo que el fondo del segundo Entry es rosa en las pantallas iOS y Android. La implementación de UWP de Entry no permite establecer el color de fondo cuando Entry está deshabilitado.

Al escribir texto en el tercer Entry, el segundo Entry cambia al estado "Normal" y el fondo es ahora verde lima:

VSM en vista: Normal

Al tocar el segundo Entry, obtiene el foco de entrada. Cambia al estado "Centrado" y se expande dos veces su altura:

VSM en vista: centrado

Observe que el Entry no conserva el fondo verde lima cuando obtiene el foco de entrada. A medida que el Administrador de estado visual cambia entre los estados visuales, las propiedades establecidas por el estado anterior no se establecen. Tenga en cuenta que los estados visuales son mutuamente excluyentes. El estado "Normal" no significa únicamente que Entry está habilitado. Significa que Entry está habilitado y no tiene el foco de entrada.

Si quiere que Entry tenga un fondo verde lima en el estado "Centrado", agregue otro Setter a ese estado visual:

<VisualState x:Name="Focused">
    <VisualState.Setters>
        <Setter Property="FontSize" Value="36" />
        <Setter Property="BackgroundColor" Value="Lime" />
    </VisualState.Setters>
</VisualState>

Para que estos objetos Setter funcionen correctamente, VisualStateGroup debe contener objetos VisualState para todos los estados de ese grupo. Si hay un estado visual que no tiene ningún objeto Setter, debe incluirse como una etiqueta vacía:

<VisualState x:Name="Normal" />

Marcado de Visual State Manager en un estilo

A menudo es necesario compartir el mismo marcado de Visual State Manager entre dos o más vistas. En este caso, querrá colocar el marcado en una definición de Style.

Este es el implícito Style existente para los elementos Entry de la página VSM en vista:

<Style TargetType="Entry">
    <Setter Property="Margin" Value="20, 0" />
    <Setter Property="FontSize" Value="18" />
</Style>

Agregue etiquetas Setter para la propiedad enlazable adjunta VisualStateManager.VisualStateGroups:

<Style TargetType="Entry">
    <Setter Property="Margin" Value="20, 0" />
    <Setter Property="FontSize" Value="18" />
    <Setter Property="VisualStateManager.VisualStateGroups">

    </Setter>
</Style>

La propiedad de contenido de Setter es Value, por lo que el valor de la propiedad Value se puede especificar directamente dentro de esas etiquetas. Esa propiedad es de tipo VisualStateGroupList:

<Style TargetType="Entry">
    <Setter Property="Margin" Value="20, 0" />
    <Setter Property="FontSize" Value="18" />
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>

        </VisualStateGroupList>
    </Setter>
</Style>

Dentro de esas etiquetas puede incluir uno de los objetos VisualStateGroup siguientes:

<Style TargetType="Entry">
    <Setter Property="Margin" Value="20, 0" />
    <Setter Property="FontSize" Value="18" />
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup x:Name="CommonStates">

            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

El resto del marcado VSM es el mismo que antes.

Esta es la página VSM en estilo que muestra el marcado VSM completo:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="VsmDemos.VsmInStylePage"
             Title="VSM in Style">
    <StackLayout>
        <StackLayout.Resources>
            <Style TargetType="Entry">
                <Setter Property="Margin" Value="20, 0" />
                <Setter Property="FontSize" Value="18" />
                <Setter Property="VisualStateManager.VisualStateGroups">
                    <VisualStateGroupList>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="Lime" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="Focused">
                                <VisualState.Setters>
                                    <Setter Property="FontSize" Value="36" />
                                    <Setter Property="BackgroundColor" Value="Lime" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="Pink" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateGroupList>
                </Setter>
            </Style>

            <Style TargetType="Label">
                <Setter Property="Margin" Value="20, 30, 20, 0" />
                <Setter Property="FontSize" Value="Large" />
            </Style>
        </StackLayout.Resources>

        <Label Text="Normal Entry:" />
        <Entry />
        <Label Text="Entry with VSM: " />
        <Entry>
            <Entry.Triggers>
                <DataTrigger TargetType="Entry"
                             Binding="{Binding Source={x:Reference entry3},
                                               Path=Text.Length}"
                             Value="0">
                    <Setter Property="IsEnabled" Value="False" />
                </DataTrigger>
            </Entry.Triggers>
        </Entry>
        <Label Text="Entry to enable 2nd Entry:" />
        <Entry x:Name="entry3"
               Text=""
               Placeholder="Type something to enable 2nd Entry" />
    </StackLayout>
</ContentPage>

Ahora todas las vistas Entry de esta página responden de la misma manera a sus estados visuales. Observe también que el estado "Centrado" ahora incluye un segundo Setter que proporciona cada Entry un fondo verde lima también cuando tiene el foco de entrada:

VSM en estilo

Estados visuales en Xamarin.Forms

En la tabla siguiente se muestran los estados visuales que se definen en Xamarin.Forms:

Clase States Más información
Button Pressed Estados visuales del botón
CheckBox IsChecked Estados visuales de CheckBox
CarouselView DefaultItem, CurrentItem, PreviousItem, NextItem Estados visuales de CarouselView
ImageButton Pressed Estados visuales de ImageButton
RadioButton Checked, Unchecked Estados visuales de RadioButton
Switch On, Off Cambiar estados visuales
VisualElement Normal, Disabled, Focused, Selected Pasos comunes

Se puede acceder a cada uno de estos estados a través del grupo de estado visual denominado CommonStates.

Además, CollectionView implementa el estado Selected. Para obtener más información, consulte Cambiar el color del elemento seleccionado.

Establecer el estado en varios elementos

En los ejemplos anteriores, los estados visuales se adjuntaron y operaron en elementos individuales. Sin embargo, también es posible crear estados visuales adjuntos a un único elemento, pero que establecen propiedades en otros elementos dentro del mismo ámbito. Esto evita tener que repetir estados visuales en cada elemento en el que operan los estados.

El tipo Setter tiene una propiedad TargetName, de tipo string, que representa el elemento de destino que manipulará el Setter para un estado visual. Cuando se define la propiedad TargetName, el Setter establece el Property del elemento definido en TargetName en Value:

<Setter TargetName="label"
        Property="Label.TextColor"
        Value="Red" />

En este ejemplo, un Label denominado label tendrá su propiedad TextColor establecida en Red. Al establecer la propiedad TargetName, debe especificar la ruta de acceso completa a la propiedad en Property. Por lo tanto, para establecer la propiedad TextColor en Label, Property se especifica como Label.TextColor.

Nota:

Cualquier propiedad a la que hace referencia un objeto Setter debe estar respaldada por una propiedad enlazable.

La página VSM con Setter TargetName del ejemplo muestra cómo establecer el estado en varios elementos desde un único grupo de estados visuales. El archivo XAML consta de un StackLayout que contiene un elemento Label, un Entry, y un Button:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="VsmDemos.VsmSetterTargetNamePage"
             Title="VSM with Setter TargetName">
    <StackLayout Margin="10">
        <Label Text="What is the capital of France?" />
        <Entry x:Name="entry"
               Placeholder="Enter answer" />
        <Button Text="Reveal answer">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="CommonStates">
                    <VisualState x:Name="Normal" />
                    <VisualState x:Name="Pressed">
                        <VisualState.Setters>
                            <Setter Property="Scale"
                                    Value="0.8" />
                            <Setter TargetName="entry"
                                    Property="Entry.Text"
                                    Value="Paris" />
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
        </Button>
    </StackLayout>
</ContentPage>

El marcado de VSM está asociado a StackLayout. Hay dos estados mutuamente excluyentes, denominados "Normal" y "Presionado", con cada estado que contiene etiquetas VisualState.

El estado "Normal" está activo cuando no se presiona el Button y se puede escribir una respuesta a la pregunta:

Setter TargetName de VSM: estado normal

El estado "Presionado" se activa cuando se presiona Button:

Setter TargetName de VSM: estado presionado

El VisualState "Presionado" especifica que cuando se presiona Button, su propiedad Scale se cambiará del valor predeterminado de 1 a 0,8. Además, Entry con nombre entry tendrá su propiedad Text establecida en París. Por lo tanto, el resultado es que cuando se presiona el Button se vuelve a escalar para que sea ligeramente más pequeño y el Entry muestra París. Después, cuando Button se libera, se vuelve a escalar a su valor predeterminado de 1 y Entry muestra cualquier texto escrito anteriormente.

Importante

Las rutas de acceso de propiedad no se admiten actualmente en elementos Setter que especifican la propiedad TargetName.

Definición de los propios estados visuales

Cada clase que deriva de VisualElement admite los estados comunes "Normal", "Enfocado" y "Deshabilitado". Además, la clase CollectionView admite el estado "Seleccionado". Internamente, la clase VisualElement detecta cuándo se habilita o deshabilita, o se centra o no se centra, y llama al método estático VisualStateManager.GoToState:

VisualStateManager.GoToState(this, "Focused");

Este es el único código de Visual State Manager que encontrará en la clase VisualElement. Dado que GoToState se llama a para cada objeto basado en cada clase que deriva de VisualElement, puede usar el Administrador de estado visual con cualquier objeto VisualElement para responder a estos cambios.

Resulta interesante que el nombre del grupo de estado visual "CommonStates" no se hace referencia explícitamente en VisualElement. El nombre del grupo no forma parte de la API del Administrador de estado visual. Dentro de uno de los dos programas de ejemplo mostrados hasta ahora, puede cambiar el nombre del grupo de "CommonStates" a cualquier otra cosa, y el programa seguirá funcionando. El nombre del grupo es simplemente una descripción general de los estados de ese grupo. Se entiende implícitamente que los estados visuales de cualquier grupo son mutuamente excluyentes: un estado y solo un estado está activo en cualquier momento.

Si quiere implementar sus propios estados visuales, deberá llamar VisualStateManager.GoToState desde el código. Con más frecuencia, realizará esta llamada desde el archivo de código subyacente de la clase de página.

En la página Validación de VSM del ejemplo se muestra cómo usar el Administrador de estado visual en relación con la validación de entrada. El archivo XAML consta de un StackLayout que contiene dos elementos Label, un Entry y un Button:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="VsmDemos.VsmValidationPage"
             Title="VSM Validation">
    <StackLayout x:Name="stackLayout"
                 Padding="10, 10">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup Name="ValidityStates">
                    <VisualState Name="Valid">
                        <VisualState.Setters>
                            <Setter TargetName="helpLabel"
                                    Property="Label.TextColor"
                                    Value="Transparent" />
                            <Setter TargetName="entry"
                                    Property="Entry.BackgroundColor"
                                    Value="Lime" />
                        </VisualState.Setters>
                    </VisualState>
                    <VisualState Name="Invalid">
                        <VisualState.Setters>
                            <Setter TargetName="entry"
                                    Property="Entry.BackgroundColor"
                                    Value="Pink" />
                            <Setter TargetName="submitButton"
                                    Property="Button.IsEnabled"
                                    Value="False" />
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
        <Label Text="Enter a U.S. phone number:"
               FontSize="Large" />
        <Entry x:Name="entry"
               Placeholder="555-555-5555"
               FontSize="Large"
               Margin="30, 0, 0, 0"
               TextChanged="OnTextChanged" />
        <Label x:Name="helpLabel"
               Text="Phone number must be of the form 555-555-5555, and not begin with a 0 or 1" />
        <Button x:Name="submitButton"
                Text="Submit"
                FontSize="Large"
                Margin="0, 20"
                VerticalOptions="Center"
                HorizontalOptions="Center" />
    </StackLayout>
</ContentPage>

El marcado de VSM está asociado a StackLayout (denominado stackLayout). Hay dos estados mutuamente excluyentes, denominados "Normal" y "Presionado", con cada estado que contiene etiquetas VisualState.

Si el Entry no contiene un número de teléfono válido, el estado actual es "No válido", por lo que el Entry tiene un fondo rosa, el segundo Label está visible y el Button está deshabilitado:

Validación de VSM: estado no válido

Cuando se escribe un número de teléfono válido, el estado actual se convierte en "Válido". Entry tiene un fondo verde lima, el segundo Label desaparece y Button ahora está habilitado:

Validación de VSM: estado válido

El archivo de código subyacente es responsable de controlar el evento TextChanged desde Entry El controlador usa una expresión regular para determinar si la cadena de entrada es válida o no. El método del archivo de código subyacente denominado GoToState llama al método estático VisualStateManager.GoToState para stackLayout:

public partial class VsmValidationPage : ContentPage
{
    public VsmValidationPage()
    {
        InitializeComponent();

        GoToState(false);
    }

    void OnTextChanged(object sender, TextChangedEventArgs args)
    {
        bool isValid = Regex.IsMatch(args.NewTextValue, @"^[2-9]\d{2}-\d{3}-\d{4}$");
        GoToState(isValid);
    }

    void GoToState(bool isValid)
    {
        string visualState = isValid ? "Valid" : "Invalid";
        VisualStateManager.GoToState(stackLayout, visualState);
    }
}

Observe también que se llama al método GoToState desde el constructor para inicializar el estado. Siempre debe haber un estado actual. Pero en ninguna parte del código hay ninguna referencia al nombre del grupo de estado visual, aunque se hace referencia a ella en el XAML como "ValidationStates" para fines de claridad.

Observe que el archivo de código subyacente solo debe tener en cuenta el objeto de la página que define los estados visuales y llamar a VisualStateManager.GoToState para este objeto. Esto se debe a que ambos estados visuales tienen como destino varios objetos en la página.

Es posible que se pregunte: Si el archivo de código subyacente debe hacer referencia al objeto en la página que define los estados visuales, ¿por qué el archivo de código subyacente simplemente puede acceder a este y a otros objetos directamente? Seguramente podría. Sin embargo, la ventaja de usar VSM es que puedes controlar cómo reaccionan los elementos visuales a un estado diferente completamente en XAML, lo que mantiene todo el diseño de la interfaz de usuario en una ubicación. Esto evita establecer la apariencia visual accediendo a los elementos visuales directamente desde el código subyacente.

Desencadenadores de estado visual

Los desencadenadores de estado visual son un conjunto especializado de desencadenadores que definen las condiciones en las que se debe aplicar VisualState.

Los desencadenadores de estado se agregan a la colección StateTriggers de una clase VisualState. Esta colección puede contener un único desencadenador de estado o varios desencadenadores de estado. Se aplicará una clase VisualState cuando cualquier desencadenador de estado de la colección esté activo.

Al usar desencadenadores de estado para controlar los estados visuales, Xamarin.Forms usa las siguientes reglas de prioridad para determinar qué desencadenador, y la clase VisualState correspondiente, se activará:

  1. Cualquier desencadenador derivado de StateTriggerBase.
  2. Una clase AdaptiveTrigger activada debido a que se cumple la condición MinWindowWidth.
  3. Una clase AdaptiveTrigger activada debido a que se cumple la condición MinWindowHeight.

Si hay varios desencadenadores activos simultáneamente (por ejemplo, dos desencadenadores personalizados), tiene prioridad el primer desencadenador declarado en el marcado.

Para obtener más información sobre desencadenadores de estado, vea Desencadenadores de estado.

Uso del Administrador de estado visual para el diseño adaptable

Una aplicación de Xamarin.Forms que se ejecuta en un teléfono normalmente se puede ver en una relación de aspecto vertical o horizontal, y un programa de Xamarin.Forms que se ejecuta en el escritorio se puede cambiar de tamaño para asumir muchos tamaños y relaciones de aspecto diferentes. Una aplicación bien diseñada podría mostrar su contenido de forma diferente para estos diversos factores de forma de página o ventana.

Esta técnica se conoce a veces como diseño adaptable. Dado que el diseño adaptable implica únicamente los objetos visuales de un programa, es una aplicación ideal del Administrador de estado visual.

Un ejemplo sencillo es una aplicación que muestra una pequeña colección de botones que afectan al contenido de la aplicación. En el modo vertical, estos botones se pueden mostrar en una fila horizontal en la parte superior de la página:

Diseño adaptativo de VSM: vertical

En el modo horizontal, es posible que la matriz de botones se mueva a un lado y se muestre en una columna:

Diseño adaptativo de VSM: horizontal

De arriba a abajo, el programa se ejecuta en la Plataforma universal de Windows, Android e iOS.

La página Diseño adaptativo de VSM del ejemplo define un grupo denominado "OrientationStates" con dos estados visuales denominados "Vertical" y "Horizontal". (Un enfoque más complejo puede basarse en varios anchos de página o ventana diferentes).

El marcado de VSM se produce en cuatro lugares del archivo XAML. El objeto StackLayout denominado mainStack contiene tanto el menú como el contenido, que es un elemento Image. StackLayout debe tener una orientación vertical en modo vertical y una orientación horizontal en modo horizontal:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="VsmDemos.VsmAdaptiveLayoutPage"
             Title="VSM Adaptive Layout">

    <StackLayout x:Name="mainStack">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="OrientationStates">
                <VisualState Name="Portrait">
                    <VisualState.Setters>
                        <Setter Property="Orientation" Value="Vertical" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState Name="Landscape">
                    <VisualState.Setters>
                        <Setter Property="Orientation" Value="Horizontal" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <ScrollView x:Name="menuScroll">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup Name="OrientationStates">
                    <VisualState Name="Portrait">
                        <VisualState.Setters>
                            <Setter Property="Orientation" Value="Horizontal" />
                        </VisualState.Setters>
                    </VisualState>
                    <VisualState Name="Landscape">
                        <VisualState.Setters>
                            <Setter Property="Orientation" Value="Vertical" />
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>

            <StackLayout x:Name="menuStack">
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup Name="OrientationStates">
                        <VisualState Name="Portrait">
                            <VisualState.Setters>
                                <Setter Property="Orientation" Value="Horizontal" />
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState Name="Landscape">
                            <VisualState.Setters>
                                <Setter Property="Orientation" Value="Vertical" />
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>

                <StackLayout.Resources>
                    <Style TargetType="Button">
                        <Setter Property="VisualStateManager.VisualStateGroups">
                            <VisualStateGroupList>
                                <VisualStateGroup Name="OrientationStates">
                                    <VisualState Name="Portrait">
                                        <VisualState.Setters>
                                            <Setter Property="HorizontalOptions" Value="CenterAndExpand" />
                                            <Setter Property="Margin" Value="10, 5" />
                                        </VisualState.Setters>
                                    </VisualState>
                                    <VisualState Name="Landscape">
                                        <VisualState.Setters>
                                            <Setter Property="VerticalOptions" Value="CenterAndExpand" />
                                            <Setter Property="HorizontalOptions" Value="Center" />
                                            <Setter Property="Margin" Value="10" />
                                        </VisualState.Setters>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateGroupList>
                        </Setter>
                    </Style>
                </StackLayout.Resources>

                <Button Text="Banana"
                        Command="{Binding SelectedCommand}"
                        CommandParameter="Banana.jpg" />
                <Button Text="Face Palm"
                        Command="{Binding SelectedCommand}"
                        CommandParameter="FacePalm.jpg" />
                <Button Text="Monkey"
                        Command="{Binding SelectedCommand}"
                        CommandParameter="monkey.png" />
                <Button Text="Seated Monkey"
                        Command="{Binding SelectedCommand}"
                        CommandParameter="SeatedMonkey.jpg" />
            </StackLayout>
        </ScrollView>

        <Image x:Name="image"
               VerticalOptions="FillAndExpand"
               HorizontalOptions="FillAndExpand" />
    </StackLayout>
</ContentPage>

El elemento interno ScrollView denominado menuScroll y el StackLayout con nombre menuStack implementan el menú de botones. La orientación de estos diseños es opuesta a mainStack. El menú debe ser horizontal en modo vertical y vertical en modo horizontal.

La cuarta sección del marcado VSM está en un estilo implícito para los propios botones. Este marcado establece las propiedades VerticalOptions, HorizontalOptionsy Margin específicas de las orientaciones verticales y horizontales.

El archivo de código subyacente establece la propiedad BindingContext de menuStack para implementar Button en comandos y también adjunta un controlador al evento SizeChanged de la página:

public partial class VsmAdaptiveLayoutPage : ContentPage
{
    public VsmAdaptiveLayoutPage ()
    {
        InitializeComponent ();

        SizeChanged += (sender, args) =>
        {
            string visualState = Width > Height ? "Landscape" : "Portrait";
            VisualStateManager.GoToState(mainStack, visualState);
            VisualStateManager.GoToState(menuScroll, visualState);
            VisualStateManager.GoToState(menuStack, visualState);

            foreach (View child in menuStack.Children)
            {
                VisualStateManager.GoToState(child, visualState);
            }
        };

        SelectedCommand = new Command<string>((filename) =>
        {
            image.Source = ImageSource.FromResource("VsmDemos.Images." + filename);
        });

        menuStack.BindingContext = this;
    }

    public ICommand SelectedCommand { private set; get; }
}

El controlador SizeChanged llama VisualStateManager.GoToState a los dos elementos StackLayout y ScrollView y, a continuación, recorre en bucle los elementos secundarios de menuStack para llamar VisualStateManager.GoToState a los elementos de Button.

Puede parecer que el archivo de código subyacente puede controlar los cambios de orientación más directamente estableciendo propiedades de elementos en el archivo XAML, pero visual State Manager es definitivamente un enfoque más estructurado. Todos los objetos visuales se conservan en el archivo XAML, donde son más fáciles de examinar, mantener y modificar.

Administrador de estado visual con Xamarin.University

Xamarin.FormsVideo del Administrador de estado visual 3.0