Xamarin.Forms Визуальный диспетчер состояний

Используйте диспетчер визуальных состояний для внесения изменений в элементы XAML на основе визуальных состояний, заданных из кода.

Visual State Manager (VSM) предоставляет структурированный способ внесения визуальных изменений в пользовательский интерфейс из кода. В большинстве случаев пользовательский интерфейс приложения определяется в XAML, и этот XAML включает разметку, описывающую, как диспетчер визуальных состояний влияет на визуальные элементы пользовательского интерфейса.

VSM представляет концепцию визуальных состояний. Представление Xamarin.Forms , например Button , может иметь несколько различных внешних представлений в зависимости от его базового состояния — отключается ли оно или нажимается или имеет фокус ввода. Это состояния кнопки.

Визуальные состояния собираются в группах визуальных состояний. Все визуальные состояния в группе визуальных состояний являются взаимоисключающими. Визуальные состояния и группы визуальных состояний определяются простыми текстовыми строками.

Диспетчер Xamarin.Forms визуальных состояний определяет одну группу визуальных состояний с именем CommonStates со следующими визуальными состояниями:

  • "Normal"
  • "Отключено"
  • "Сосредоточено"
  • "Выбрано"

Эта группа визуальных состояний поддерживается для всех классов, производных от VisualElement, который является базовым классом для View и Page.

Вы также можете определить собственные группы визуальных состояний и визуальные состояния, как показано в этой статье.

Примечание.

Xamarin.Forms Разработчики, знакомые с триггерами, знают, что триггеры также могут вносить изменения в визуальные элементы в пользовательском интерфейсе на основе изменений свойств представления или запуска событий. Однако использование триггеров для работы с различными сочетаниями этих изменений может стать довольно запутанным. Исторически диспетчер визуальных состояний был представлен в средах на основе Windows XAML, чтобы облегчить путаницу, полученную из сочетаний визуальных состояний. При использовании VSM визуальные состояния в группе визуальных состояний всегда являются взаимоисключающими. В любое время только одно состояние в каждой группе — текущее состояние.

Общие состояния

Диспетчер визуальных состояний позволяет включить разметку в XAML-файл, который может изменить внешний вид представления, если представление нормально или отключено или имеет фокус ввода. Они называются общими состояниями.

Например, предположим, что на странице есть Entry представление, и вы хотите, чтобы внешний вид визуального Entry элемента изменился следующим образом:

  • Должен Entry иметь розовый фон при Entry отключении.
  • Должен Entry иметь фон лайма обычно.
  • Должно Entry увеличиться до дважды его нормальной высоты, если он имеет фокус ввода.

Вы можете присоединить разметку VSM к отдельному представлению или определить ее в стиле, если он применяется к нескольким представлениям. В следующих двух разделах описаны эти подходы.

Разметка VSM в представлении

Чтобы присоединить разметку VSM к представлению Entry , сначала разделите их на начальные Entry и конечные теги:

<Entry FontSize="18">

</Entry>

Он получает явный размер шрифта, так как одно из состояний будет использовать FontSize свойство для двойного размера текста в нем Entry.

Затем вставьте VisualStateManager.VisualStateGroups теги между этими тегами:

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

    </VisualStateManager.VisualStateGroups>
</Entry>

VisualStateGroups является присоединенным привязываемым свойством, определенным классом VisualStateManager . (Дополнительные сведения о присоединенных привязываемых свойствах см. в статье Присоединенные свойства.) Вот как VisualStateGroups свойство присоединено к объекту Entry .

Свойство VisualStateGroups имеет тип VisualStateGroupList, который является коллекцией VisualStateGroup объектов. VisualStateManager.VisualStateGroups В тегах вставьте пару тегов VisualStateGroup для каждой группы визуальных состояний, которые необходимо включить:

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

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

Обратите внимание, что VisualStateGroup тег имеет атрибут, указывающий x:Name имя группы. Класс VisualStateGroup определяет Name свойство, которое можно использовать вместо этого:

<VisualStateGroup Name="CommonStates">

В одном элементе можно использовать x:Name либо или Name не оба элемента.

Класс VisualStateGroup определяет свойство с именем States, которое является коллекцией VisualState объектов. States— это свойство содержимогоVisualStateGroups, которое позволяет включать VisualState теги непосредственно между VisualStateGroup тегами. (Свойства содержимого рассматриваются в статье Основной синтаксис XAML.)

Следующим шагом является включение пары тегов для каждого визуального состояния в этой группе. Их также можно определить с помощью x:Name или 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 определяет свойство с именем Setters, которое является коллекцией Setter объектов. Это те же Setter объекты, которые используются в объекте Style .

Settersне является свойством содержимогоVisualState, поэтому необходимо включить теги элементов свойства для 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>

Теперь можно вставить один или несколько Setter объектов между каждой парой тегов Setters . Это объекты, определяющие визуальные Setter состояния, описанные ранее:

<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>

Каждый Setter тег указывает значение определенного свойства при текущем состоянии. Любое свойство, на которое Setter ссылается объект, должно поддерживаться привязываемым свойством.

Разметка , аналогичная этой, является основой страницы VSM на странице представления в примере программы. Страница содержит три Entry представления, но только второй содержит разметку VSM, подключенную к ней:

<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>

Обратите внимание, что второй Entry также имеет DataTrigger часть своей Trigger коллекции. Это приводит Entry к отключению, пока что-то не будет введено в третью Entry. Ниже приведена страница при запуске в iOS, Android и универсальная платформа Windows (UWP):

VSM в режиме просмотра: отключен

Текущее визуальное состояние "Отключено", поэтому фон второго Entry розовый на экранах iOS и Android. Реализация UWP Entry не позволяет задать цвет фона при Entry отключении.

При вводе текста в третий Entry, второй Entry переключается в состояние "Обычный", а фон теперь является лаймом:

VSM в представлении: обычный

При касании второго Entryон получает фокус ввода. Он переключается на состояние "Фокус" и расширяется до дважды его высоты:

VSM в представлении: сосредоточено

Обратите внимание, что фон Entry лайма не сохраняется при получении фокуса ввода. Так как диспетчер визуальных состояний переключается между визуальными состояниями, свойства, заданные предыдущим состоянием, не заданы. Помните, что визуальные состояния являются взаимоисключающими. Состояние "Обычный" не означает только то, что Entry он включен. Это означает, что включен Entry и не имеет фокуса ввода.

Если вы хотите Entry , чтобы фон лайма был в состоянии "Фокус", добавьте еще один Setter в это визуальное состояние:

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

Чтобы эти Setter объекты работали правильно, VisualStateGroup необходимо содержать VisualState объекты для всех состояний в этой группе. Если есть визуальное состояние, которое не имеет Setter объектов, добавьте его в любом случае в качестве пустого тега:

<VisualState x:Name="Normal" />

Разметка Visual State Manager в стиле

Часто необходимо совместно использовать одну разметку диспетчера визуальных состояний между двумя или более представлениями. В этом случае необходимо поместить разметку в Style определение.

Ниже приведены неявные Style элементы Entry на странице представления VSM:

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

Добавьте Setter теги для присоединенного VisualStateManager.VisualStateGroups привязываемого свойства:

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

    </Setter>
</Style>

Свойство содержимого Value для Setter имеет значениеValue, поэтому значение свойства можно указать непосредственно в этих тегах. Это свойство имеет тип VisualStateGroupList:

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

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

В этих тегах можно включить один из нескольких VisualStateGroup объектов:

<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>

Оставшаяся часть разметки VSM совпадает с тем же, что и раньше.

Ниже приведена страница VSM в стиле , на которой показана полная разметка VSM:

<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>

Теперь все Entry представления на этой странице реагируют одинаково на их визуальные состояния. Обратите внимание также, что состояние "Фокус" теперь включает в себя секунду Setter , которая дает каждому Entry фону лайма, также если он имеет фокус ввода:

VSM в стиле

Визуальные состояния в Xamarin.Forms

В следующей таблице перечислены визуальные состояния, определенные в Xamarin.Forms:

Класс Состояния Дополнительные сведения
Button Pressed Визуальные состояния кнопки
CheckBox IsChecked Визуальные состояния CheckBox
CarouselView DefaultItem, , CurrentItemPreviousItemNextItem Визуальные состояния CarouselView
ImageButton Pressed Визуальные состояния ImageButton
RadioButton Checked, Unchecked Визуальные состояния RadioButton
Switch On, Off Переключение визуальных состояний
VisualElement Normal, , DisabledFocusedSelected Общие состояния

Доступ к каждому из этих состояний можно получить через группу визуальных состояний с именем CommonStates.

Кроме того, он CollectionView реализует Selected состояние. Дополнительные сведения см. в разделе "Изменение цвета выбранного элемента".

Установка состояния для нескольких элементов

В предыдущих примерах визуальные состояния были присоединены и работали с одними элементами. Однако также можно создать визуальные состояния, присоединенные к одному элементу, но которые задают свойства для других элементов в той же области. Это позволяет избежать необходимости повторять визуальные состояния для каждого элемента, над которыми работают состояния.

Тип Setter имеет TargetName свойство типа, stringпредставляющее целевой элемент, который Setter будет управлять визуальным состоянием. TargetName При определении свойства задает Property элемент, Setter определенный в TargetName Value:

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

В этом примере у именованного Label label свойства будет TextColor задано значение Red. При задании TargetName свойства необходимо указать полный путь к свойству.Property Таким образом, чтобы задать TextColor свойство в объекте Label, Property указывается как Label.TextColor.

Примечание.

Любое свойство, на которое Setter ссылается объект, должно поддерживаться привязываемым свойством.

На странице VSM с Setter TargetName в примере показано, как задать состояние для нескольких элементов из одной визуальной группы состояний. XAML-файл состоит из StackLayout элементаLabel, элемента, а Entryтакже :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>

Разметка VSM присоединена к StackLayout. Существует два взаимоисключающих состояния с именем "Normal" и "Pressed", с каждым состоянием VisualState , содержащим теги.

Состояние "Обычный" активируется, если Button нажатие не нажато, и можно ввести ответ на вопрос:

Целевое имя набора VSM: обычное состояние

Состояние "Нажато" становится активным при Button нажатии:

Имя целевого объекта задания VSM: нажатое состояние

VisualState Нажатие клавиши указывает, что при Button нажатии его Scale свойство будет изменено со значения по умолчанию от 1 до 0,8. Кроме того, именованный Entry entry будет иметь его Text свойство, установленное в Париже. Таким образом, результат заключается в том, что при Button нажатии он перемасштабируется, чтобы быть немного меньше, и Entry отображение Парижа. Затем, когда Button он выпущен, он перемасштабируется до значения по умолчанию 1, и Entry отображает любой ранее введенный текст.

Внимание

Пути свойств в настоящее время не поддерживаются в Setter элементах, которые указывают TargetName свойство.

Определение собственных визуальных состояний

Каждый класс, производный от VisualElement поддержки общих состояний "Normal", "Focused" и "Disabled". Кроме того, CollectionView класс поддерживает состояние Selected. VisualElement Внутри него класс обнаруживает, когда он становится включенным или отключенным, а также сосредоточен или не ориентирован, а также вызывает статический VisualStateManager.GoToState метод:

VisualStateManager.GoToState(this, "Focused");

Это единственный код Visual State Manager, который вы найдете в VisualElement классе. Так как GoToState вызывается для каждого объекта на основе каждого класса, наследуемого от VisualElementкаждого класса, можно использовать диспетчер визуальных состояний с любым VisualElement объектом для реагирования на эти изменения.

Интересно, что имя визуальной группы состояний CommonStates не ссылается явным образом.VisualElement Имя группы не является частью API для диспетчера визуальных состояний. В одной из двух примеров программы, показанной до сих пор, вы можете изменить имя группы с CommonStates на что-либо другое, и программа по-прежнему будет работать. Имя группы — это просто общее описание состояний в этой группе. Неявно понятно, что визуальные состояния в любой группе являются взаимоисключающими: одно состояние и только одно состояние в любое время является текущим.

Если вы хотите реализовать собственные визуальные состояния, необходимо вызвать VisualStateManager.GoToState из кода. Чаще всего этот вызов выполняется из файла программной части класса страницы.

На странице проверки VSM в примере показано, как использовать визуальный диспетчер состояний в связи с проверкой входных данных. XAML-файл состоит из StackLayout двух Label элементов, а Entryтакже :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>

Разметка VSM присоединена к StackLayout (именованному stackLayout). Существует два взаимоисключающих состояния с именем "Допустимый" и "Недопустимый" с каждым состоянием VisualState , содержащим теги.

Entry Если номер телефона не содержит допустимый, текущее состояние равно "Недопустимо" и поэтому Entry имеет розовый фон, второй Label отображается и Button отключен:

Проверка VSM: недопустимое состояние

При вводе допустимого номера телефона текущее состояние становится допустимым. Получает Entry фон лайма, второй Label исчезает и Button теперь включен:

Проверка VSM: допустимое состояние

Файл программной части отвечает за обработку TextChanged события из файла Entry. Обработчик использует регулярное выражение, чтобы определить, является ли входная строка допустимой или нет. Метод в файле программной части с именем GoToState вызывает статический VisualStateManager.GoToState метод для 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);
    }
}

Обратите внимание, что GoToState метод вызывается из конструктора для инициализации состояния. Всегда должно быть текущее состояние. Но нигде в коде нет ссылки на имя визуальной группы состояний, хотя она ссылается на XAML как "ValidationStates" для ясности.

Обратите внимание, что файл программной части должен учитывать только объект на странице, определяющей визуальные состояния, и вызывать VisualStateManager.GoToState этот объект. Это связано с тем, что оба визуальных состояния предназначены для нескольких объектов на странице.

Вы можете задаться вопросом: если файл кода программной части должен ссылаться на объект на странице, определяющей визуальные состояния, почему файл программной части не может просто получить доступ к этому и другим объектам напрямую? Это, безусловно, может. Однако преимущество использования VSM заключается в том, что визуальные элементы могут управлять тем, как визуальные элементы реагируют на другое состояние полностью в XAML, что сохраняет весь дизайн пользовательского интерфейса в одном расположении. Это позволяет избежать настройки внешнего вида, доступ к визуальным элементам непосредственно из программной части.

Триггеры визуального состояния

Визуальные состояния поддерживают триггеры состояния, которые представляют собой специализированную группу триггеров, определяющих условия применения VisualState .

Триггеры состояния добавляются в коллекцию StateTriggers VisualState. Эта коллекция может содержать один или несколько триггеров состояния. При наличии активных триггеров состояния в коллекции будет применяться VisualState.

При использовании триггеров состояния для управления визуальными состояниями Xamarin.Forms применяет следующие правила приоритета, чтобы определить, какой триггер (и соответственно VisualState) будет активен:

  1. Любой триггер, производный от StateTriggerBase.
  2. AdaptiveTrigger активируется из-за выполнения условия MinWindowWidth.
  3. AdaptiveTrigger активируется из-за выполнения условия MinWindowHeight.

Если одновременно активны несколько триггеров (например, два пользовательских триггера), то у первого триггера, объявленного в разметке, будет приоритет.

Дополнительные сведения о триггерах состояния см. в разделе Триггеры состояния.

Использование диспетчера визуальных состояний для адаптивного макета

Приложение Xamarin.Forms , работающее на телефоне, обычно можно просматривать в книжном или альбомном пропорции, а Xamarin.Forms программа, работающая на рабочем столе, может быть изменена, чтобы предположить множество различных размеров и пропорций. Хорошо разработанное приложение может отображать его содержимое по-разному для этих различных форм-факторов страницы или окна.

Этот метод иногда называется адаптивным макетом. Так как адаптивный макет исключительно включает визуальные элементы программы, это идеальное приложение диспетчера визуальных состояний.

Простой пример — это приложение, отображающее небольшую коллекцию кнопок, влияющих на содержимое приложения. В книжном режиме эти кнопки могут отображаться в горизонтальной строке в верхней части страницы:

Адаптивный макет VSM: книжный

В альбомном режиме массив кнопок может перемещаться на одну сторону и отображаться в столбце:

Адаптивный макет VSM: альбомная

Сверху вниз программа выполняется в универсальная платформа Windows, Android и iOS.

Страница адаптивного макета VSM в примере определяет группу с именем "OrientationStates" с двумя визуальными состояниями "Книжный" и "Альбом". (Более сложный подход может быть основан на нескольких разных ширинах страницы или окна.)

Разметка VSM выполняется в четырех местах в XAML-файле. Имя StackLayout mainStack содержит как меню, так и содержимое, которое является элементом Image . Это StackLayout должно иметь вертикальную ориентацию в книжном режиме и горизонтальной ориентации в альбомном режиме:

<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>

Внутреннее ScrollView имя menuScroll и StackLayout именованное menuStack меню кнопок. Ориентация этих макетов отличается от mainStack. Меню должно быть горизонтально в книжном режиме и вертикальном режиме в альбомном режиме.

Четвертый раздел разметки VSM находится в неявном стиле для кнопок. Эти наборы VerticalOptionsHorizontalOptionsразметки и Margin свойства, относящиеся к книжной и альбомной ориентации.

Файл программной части задает BindingContext свойство menuStack реализации Button команд, а также присоединяет обработчик к SizeChanged событию страницы:

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; }
}

Обработчик SizeChanged вызывает VisualStateManager.GoToState два StackLayout элемента и ScrollView элементы, а затем выполняет цикл по дочерним menuStack элементам для Button вызова VisualStateManager.GoToState элементов.

Может показаться, что если файл кода программной части может обрабатывать изменения ориентации более непосредственно путем задания свойств элементов в XAML-файле, но визуальный диспетчер состояний, безусловно, более структурированный подход. Все визуальные элементы хранятся в XAML-файле, где они становятся проще изучить, поддерживать и изменять.

Visual State Manager с Xamarin.University

Xamarin.Forms Видео диспетчера визуальных состояний 3.0