Xamarin.Forms 视觉状态管理器
使用视觉状态管理器根据代码中设置的视觉状态对 XAML 元素进行更改。
Visual State Manager (VSM) 提供了一种结构化方式,用于通过代码对用户界面进行可视更改。 在大多数情况下,应用程序的用户界面是在 XAML 中定义的,此 XAML 包含描述视觉状态管理器如何影响用户界面视觉对象的标记。
VSM 引入了 视觉状态的概念。 Xamarin.Forms视图(如 Button
)可以具有多种不同的视觉外观,具体取决于其基础状态, 是禁用、按下还是具有输入焦点。 这些是按钮的状态。
视觉状态收集在 视觉状态组中。 视觉状态组中的所有视觉状态都是互斥的。 视觉状态和视觉状态组都由简单的文本字符串标识。
视觉 Xamarin.Forms 状态管理器定义了一个名为“CommonStates”的视觉状态组,该组具有以下视觉状态:
- "Normal"
- “Disabled”
- “Focused”
- “Selected”
派生自 VisualElement
的所有类都支持此视觉状态组,后者是 和 Page
的View
基类。
还可以定义自己的视觉状态组和视觉状态,如本文所示。
注意
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
的属性,该属性是 对象的集合 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
。 这些对象与在 对象中使用的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) 上运行的启动时的页面:
当前视觉状态为“已禁用”,因此第二 Entry
个屏幕的背景在 iOS 和 Android 屏幕上为粉红色。 禁用 时Entry
, 的 Entry
UWP 实现不允许设置背景色。
在第三 Entry
个 中输入一些文本时,第二 Entry
个将切换到“正常”状态,背景现在为石灰:
当你触摸第二 Entry
个 时,它将获取输入焦点。 它切换到“聚焦”状态,并扩展到其高度的两倍:
请注意, 在 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
设置一个石灰背景:
中的视觉状态 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
为 ,该属性表示视觉状态的 将操作的目标元素 Setter
。 TargetName
定义 属性后, 将 Setter
中TargetName
定义的元素的 设置为 Property
Value
:
<Setter TargetName="label"
Property="Label.TextColor"
Value="Red" />
在此示例中,名为 的 Label
属性设置为 TextColor
Red
。label
设置 属性时, TargetName
必须在 中 Property
指定属性的完整路径。 因此,若要在 上设置 TextColor
属性,Property
请将 指定为 Label.TextColor
。Label
注意
对象引用 Setter
的任何属性都必须由可绑定属性提供支持。
VsmDemos 示例中的 VSM with Setter TargetName 页显示如何从单个视觉状态组设置多个元素的状态。 XAML 文件由包含 Label
元素的 StackLayout
、 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
,“正常”状态处于活动状态,并且可以输入对问题的响应:
按下 时 Button
,“已按下”状态变为活动状态:
“Pressed” VisualState
指定按下 时 Button
,其 Scale
属性将从默认值 1 更改为 0.8。 此外,命名 entry
的 Entry
属性将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 文件由包含两Label
个StackLayout
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
) 的 (。 有两个互斥状态,分别名为“Valid”和“Invalid”,每个状态都包含 VisualState
标记。
Entry
如果 不包含有效的电话号码,则当前状态为“无效”,因此 Entry
具有粉色背景,第二Label
个为可见,并且 Button
已禁用:
输入有效的电话号码后,当前状态将变为“有效”。 获取 Entry
一个石灰背景,第二 Label
个消失,现在 Button
已启用:
代码隐藏文件负责处理 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
。
状态触发器添加到 VisualState
的 StateTriggers
集合。 此集合可以包含一个或多个状态触发器。 当此集合中的任何状态触发器处于活动状态时,便会应用 VisualState
。
使用状态触发器来控制视觉对象状态时,Xamarin.Forms 使用以下优先规则来确定哪个触发器(以及相应的 VisualState
)处于活动状态:
- 任何派生自
StateTriggerBase
的触发器。 - 因满足
MinWindowWidth
条件而激活的AdaptiveTrigger
。 - 因满足
MinWindowHeight
条件而激活的AdaptiveTrigger
。
如果多个触发器同时处于活动状态(例如,两个自定义触发器),则标记中声明的第一个触发器优先。
有关状态触发器的详细信息,请参阅状态触发器。
使用视觉状态管理器进行自适应布局
Xamarin.Forms通常,可以在纵向或横向纵横比中查看手机上运行的应用程序,并且可以Xamarin.Forms调整在桌面上运行的程序的大小,以采用许多不同的大小和纵横比。 对于这些不同的页面或窗口外形规格,设计良好的应用程序可能会以不同的方式显示其内容。
此方法有时称为 自适应布局。 由于自适应布局仅涉及程序的视觉对象,因此它是视觉状态管理器的理想应用程序。
一个简单的示例是显示影响应用程序内容的一小部分按钮的应用程序。 在纵向模式下,这些按钮可能显示在页面顶部的水平行中:
在横向模式下,按钮数组可能会移动到一侧,并显示在列中:
从上到下,程序在 通用 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
和 命名menuStack
的 StackLayout
实现按钮的menuScroll
菜单。 这些布局的方向与 相反 mainStack
。 在纵向模式下,菜单应是水平菜单,在横向模式下应为垂直菜单。
VSM 标记的第四部分是按钮本身的隐式样式。 此标记设置VerticalOptions
特定于纵向和Margin
横向的 、 HorizontalOptions
和 属性。
代码隐藏文件设置 BindingContext
的 属性以实现Button
命令,并将处理程序附加到SizeChanged
menuStack
页面的 事件:
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 文件中设置元素的属性来更直接地处理方向更改,但 Visual State Manager 绝对是一种更具结构化的方法。 所有视觉对象都保存在 XAML 文件中,以便更轻松地检查、维护和修改它们。
Visual State Manager with Xamarin.University
Xamarin.Forms 3.0 Visual State Manager 视频