Xamarin.Forms 视觉状态管理器

下载示例 下载示例

使用视觉状态管理器根据代码中设置的视觉状态对 XAML 元素进行更改。

Visual State Manager (VSM) 提供了一种结构化方式,用于通过代码对用户界面进行可视更改。 在大多数情况下,应用程序的用户界面是在 XAML 中定义的,此 XAML 包含描述视觉状态管理器如何影响用户界面视觉对象的标记。

VSM 引入了 视觉状态的概念。 Xamarin.Forms视图(如 Button )可以具有多种不同的视觉外观,具体取决于其基础状态, 是禁用、按下还是具有输入焦点。 这些是按钮的状态。

视觉状态收集在 视觉状态组中。 视觉状态组中的所有视觉状态都是互斥的。 视觉状态和视觉状态组都由简单的文本字符串标识。

视觉 Xamarin.Forms 状态管理器定义了一个名为“CommonStates”的视觉状态组,该组具有以下视觉状态:

  • "Normal"
  • “Disabled”
  • “Focused”
  • “Selected”

派生自 VisualElement的所有类都支持此视觉状态组,后者是 和 PageView基类。

还可以定义自己的视觉状态组和视觉状态,如本文所示。

注意

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 附加可绑定属性。 (有关附加的可绑定属性的详细信息,请参阅附加 properties.) 这是属性附加到Entry对象的方式VisualStateGroups

属性 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的属性,该属性是 对象的集合 VisualStateStates的内容属性VisualStateGroups 因此可以直接在 VisualState 标记之间 VisualStateGroup 包括标记。 (内容属性在 基本 XAML 语法一文中进行了讨论。)

下一步是包含该组中每个视觉状态的一对标记。 还可以使用 x:NameName来标识这些项:

<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 。 这些对象与在 对象中使用的Style对象相同Setter

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>

现在可以在每对Setters标记之间插入一个或多个Setter对象。 这些对象 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 的任何属性都必须由可绑定属性提供支持。

与此类似的标记是 VsmDemos 示例程序中“视图”页上的 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个也具有 作为其Trigger集合的一DataTrigger部分。 这会导致禁用 , Entry 直到将某些内容键入到第三 Entry个 中。 下面是在 iOS、Android 和 通用 Windows 平台 (UWP) 上运行的启动时的页面:

视图上的 VSM:视图

当前视觉状态为“已禁用”,因此第二 Entry 个屏幕的背景在 iOS 和 Android 屏幕上为粉红色。 禁用 时Entry, 的 Entry UWP 实现不允许设置背景色。

在第三 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" />

样式中的视觉状态管理器标记

通常需要在两个或更多视图之间共享相同的视觉状态管理器标记。 在这种情况下,需要将标记放入定义中 Style

下面是 VSM On View 页中Entry元素的现有隐式Style

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

为附加的VisualStateManager.VisualStateGroups可绑定属性添加Setter标记:

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

    </Setter>
</Style>

的内容 Setter 属性为 Value,因此可以直接在这些标记中指定属性的值 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, CurrentItem, PreviousItem, NextItem CarouselView 视觉状态
ImageButton Pressed ImageButton 视觉状态
RadioButton Checked, Unchecked RadioButton 视觉状态
Switch On, Off 切换视觉状态
VisualElement Normal, Disabled, Focused, Selected 常见状态

可以通过名为 CommonStates的视觉状态组访问其中每个状态。

此外, CollectionView 实现 Selected 状态。 有关详细信息,请参阅 更改所选项颜色

在多个元素上设置状态

在前面的示例中,视觉状态附加到单个元素并针对单个元素进行操作。 但是,也可以创建附加到单个元素的视觉状态,但对同一范围内的其他元素设置属性。 这避免了在运行状态的每个元素上重复视觉状态。

Setter 类型具有一个 TargetName 属性,该属性的类型 string为 ,该属性表示视觉状态的 将操作的目标元素 SetterTargetName定义 属性后, 将 SetterTargetName定义的元素的 设置为 PropertyValue

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

在此示例中,名为 的 Label 属性设置为 TextColorRedlabel 设置 属性时, TargetName 必须在 中 Property指定属性的完整路径。 因此,若要在 上设置 TextColor 属性,Property请将 指定为 Label.TextColorLabel

注意

对象引用 Setter 的任何属性都必须由可绑定属性提供支持。

VsmDemos 示例中的 VSM with Setter TargetName 页显示如何从单个视觉状态组设置多个元素的状态。 XAML 文件由包含 Label 元素的 StackLayoutEntryButton组成:

<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 Setter TargetName:正常状态

按下 时 Button ,“已按下”状态变为活动状态:

VSM Setter TargetName:按下状态

“Pressed” VisualState 指定按下 时 Button ,其 Scale 属性将从默认值 1 更改为 0.8。 此外,命名 entryEntry 属性将Text设置为 Paris。 因此,结果是当按下 时 Button ,会将其重新缩放为略小,并且 Entry 显示 Paris。 然后,当释放 时 Button ,它会重新缩放到其默认值 1,并 Entry 显示以前输入的任何文本。

重要

指定TargetName属性的Setter元素当前不支持属性路径。

定义自己的视觉状态

派生自 VisualElement 的每个类都支持常见状态“Normal”、“Focused”和“Disabled”。 此外, CollectionView 类支持“Selected”状态。 在内部, VisualElement 类检测何时启用或禁用、聚焦或未聚焦,并调用静态 VisualStateManager.GoToState 方法:

VisualStateManager.GoToState(this, "Focused");

这是类中 VisualElement 唯一的 Visual State Manager 代码。 由于 GoToState 基于派生自 VisualElement的每个类为每个对象调用 ,因此可以将视觉状态管理器与任何 VisualElement 对象一起使用来响应这些更改。

有趣的是,视觉状态组“CommonStates”的名称未在 中 VisualElement显式引用。 组名称不是视觉状态管理器 API 的一部分。 在目前显示的两个示例程序之一中,可以将组名称从“CommonStates”更改为任何其他程序,程序仍可正常工作。 组名称只是该组中状态的一般说明。 可以隐式理解,任何组中的视觉状态都是互斥的:一个状态,任何时候只有一个状态是最新的。

如果要实现自己的视觉状态,则需要从代码调用 VisualStateManager.GoToState 。 大多数情况下,你会从页面类的代码隐藏文件进行此调用。

VsmDemos 示例中的“VSM 验证”页显示如何使用视觉状态管理器与输入验证相关联。 XAML 文件由包含两LabelStackLayoutEntry元素的 、 和 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) 的 (。 有两个互斥状态,分别名为“Valid”和“Invalid”,每个状态都包含 VisualState 标记。

Entry如果 不包含有效的电话号码,则当前状态为“无效”,因此 Entry 具有粉色背景,第二Label个为可见,并且 Button 已禁用:

VSM 验证:无效状态

输入有效的电话号码后,当前状态将变为“有效”。 获取 Entry 一个石灰背景,第二 Label 个消失,现在 Button 已启用:

VSM 验证:有效状态

代码隐藏文件负责处理 TextChanged 来自 Entry的事件。 处理程序使用正则表达式来确定输入字符串是否有效。 名为 的代码隐藏文件中的 GoToState 方法调用 的stackLayout静态VisualStateManager.GoToState方法:

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 中对不同状态做出反应,从而将所有 UI 设计保存在一个位置。 这可以通过直接从代码隐藏访问视觉对象元素来避免设置视觉外观。

视觉状态触发器

视觉状态支持状态触发器,状态触发器是一组专用的触发器,用于定义应用 的条件 VisualState

状态触发器添加到 VisualStateStateTriggers 集合。 此集合可以包含一个或多个状态触发器。 当此集合中的任何状态触发器处于活动状态时,便会应用 VisualState

使用状态触发器来控制视觉对象状态时,Xamarin.Forms 使用以下优先规则来确定哪个触发器(以及相应的 VisualState)处于活动状态:

  1. 任何派生自 StateTriggerBase 的触发器。
  2. 因满足 MinWindowWidth 条件而激活的 AdaptiveTrigger
  3. 因满足 MinWindowHeight 条件而激活的 AdaptiveTrigger

如果多个触发器同时处于活动状态(例如,两个自定义触发器),则标记中声明的第一个触发器优先。

有关状态触发器的详细信息,请参阅状态触发器

使用视觉状态管理器进行自适应布局

Xamarin.Forms通常,可以在纵向或横向纵横比中查看手机上运行的应用程序,并且可以Xamarin.Forms调整在桌面上运行的程序的大小,以采用许多不同的大小和纵横比。 对于这些不同的页面或窗口外形规格,设计良好的应用程序可能会以不同的方式显示其内容。

此方法有时称为 自适应布局。 由于自适应布局仅涉及程序的视觉对象,因此它是视觉状态管理器的理想应用程序。

一个简单的示例是显示影响应用程序内容的一小部分按钮的应用程序。 在纵向模式下,这些按钮可能显示在页面顶部的水平行中:

VSM 自适应布局:纵向

在横向模式下,按钮数组可能会移动到一侧,并显示在列中:

VSM 自适应布局:横向

从上到下,程序在 通用 Windows 平台、Android 和 iOS 上运行。

VsmDemos 示例中的 VSM 自适应布局页定义了一个名为“OrientationStates”的组,该组具有名为“Portrait”和“Landscape”的两个视觉状态。 (更复杂的方法可能基于多个不同的页面或窗口宽度。)

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 和 命名menuStackStackLayout 实现按钮的menuScroll菜单。 这些布局的方向与 相反 mainStack。 在纵向模式下,菜单应是水平菜单,在横向模式下应为垂直菜单。

VSM 标记的第四部分是按钮本身的隐式样式。 此标记设置VerticalOptions特定于纵向和Margin横向的 、 HorizontalOptions和 属性。

代码隐藏文件设置 BindingContext 的 属性以实现Button命令,并将处理程序附加到SizeChangedmenuStack页面的 事件:

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.GoToStateStackLayout个 和 ScrollView 元素,然后循环访问 的menuStack子元素,以便为Button元素调用 VisualStateManager.GoToState

代码隐藏文件似乎可以通过在 XAML 文件中设置元素的属性来更直接地处理方向更改,但 Visual State Manager 绝对是一种更具结构化的方法。 所有视觉对象都保存在 XAML 文件中,以便更轻松地检查、维护和修改它们。

Visual State Manager with Xamarin.University

Xamarin.Forms 3.0 Visual State Manager 视频