Xamarin.Forms Visual State Manager
コードから設定された表示状態に基づいて XAML 要素を変更するには、Visual State Manager を使用します。
Visual State Manager (VSM) は、コードからユーザー インターフェイスに視覚的な変更を加える構造化された方法を提供します。 ほとんどの場合、アプリケーションのユーザー インターフェイスは XAML で定義され、この XAML には、Visual State Manager がユーザー インターフェイスのビジュアルに与える影響を説明するマークアップが含まれています。
VSM では、表示状態の概念が導入されています。 Button
などの Xamarin.Forms ビューは、基になる状態 (無効になっているか、押されているか、入力フォーカスがあるか) に応じて、いくつかの異なる外観を持つことができます。 これらはボタンの状態です。
表示状態は、表示状態グループで収集されます。 表示状態グループ内のすべての表示状態は、互いに排他的です。 表示状態と表示状態グループはどちらも、単純なテキスト文字列によって識別されます。
Xamarin.Forms Visual State Manager は、"CommonStates" という名前の 1 つの表示状態グループを、次の表示状態で定義します。
- "Normal"
- "Disabled"
- "Focused"
- "Selected"
この表示状態グループは、VisualElement
(View
および Page
の基底クラス) から派生するすべてのクラスでサポートされています。
この記事で示すように、独自の表示状態グループと表示状態を定義することもできます。
Note
トリガーを使い慣れている Xamarin.Forms 開発者はお気付きのように、トリガーでも、ビューのプロパティの変更やイベントの発生に基づいて、ユーザー インターフェイスのビジュアルに変更が加えられる場合があります。 ただし、トリガーを使用してこれらの変更のさまざまな組み合わせに対処すると、混乱を招く可能性があります。 これまで、Visual State Manager は、表示状態の組み合わせによる混乱を軽減するために、Windows XAML ベースの環境で導入されていました。 VSM では、表示状態グループ内の表示状態は常に相互に排他的です。 常に、各グループ内の 1 つの状態のみが現在の状態です。
一般的な状態
Visual State Manager を使用すると、ビューが正常であるか無効になっているか、入力フォーカスがある場合にビューの外観を変更できるマークアップを XAML ファイルに含めることができます。 これらは一般的な状態として知られています。
たとえば、ページに Entry
ビューがあり、Entry
の外観を次のように変更するとします。
Entry
が無効になっている場合、Entry
の背景はピンク色になります。Entry
の背景は通常、ライム色になっているはずです。Entry
は、入力フォーカスがある場合、通常の高さの 2 倍に拡張されます。
VSM マークアップを個々のビューにアタッチできます。または、複数のビューに適用する場合は、スタイルで定義することもできます。 次の 2 つのセクションでは、これらのアプローチについて説明します。
ビューの VSM マークアップ
VSM マークアップを Entry
ビューにアタッチするには、最初に Entry
を開始タグと終了タグに分けます。
<Entry FontSize="18">
</Entry>
明示的なフォント サイズが指定されているのは、状態の 1 つが FontSize
プロパティを使用して Entry
のテキストのサイズを 2 倍にするためです。
次に、これらのタグの間に VisualStateManager.VisualStateGroups
タグを挿入します。
<Entry FontSize="18">
<VisualStateManager.VisualStateGroups>
</VisualStateManager.VisualStateGroups>
</Entry>
VisualStateGroups
は、VisualStateManager
クラスによって定義される、アタッチされたバインド可能なプロパティです。 (アタッチされたバインド可能なプロパティの詳細については、記事「添付プロパティ」を参照してください)。これは、VisualStateGroups
プロパティがどのように Entry
オブジェクトにアタッチされるかを示しています。
VisualStateGroupList
型の VisualStateGroups
プロパティは、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
クラスは、VisualState
オブジェクトのコレクションである States
という名前のプロパティを定義します。 States
は VisualStateGroups
のコンテンツのプロパティであるため、VisualStateGroup
タグ間に VisualState
タグを直接含めることができます。 (コンテンツのプロパティについては、基本的な 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
は、Setter
オブジェクトのコレクションである、Setters
という名前のプロパティを定義します。 これらは、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
タグの各ペアの間に 1 つまたは複数の 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
オブジェクトによって参照されるすべてのプロパティは、バインド可能なプロパティによってサポートされている必要があります。
これに似たマークアップは、サンプル プログラムのビュー上の VSM ページの基礎になっています。 このページには 3 つの Entry
ビューが含まれていますが、VSM マークアップがアタッチされているのは 2 番目のビューだけです。
<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>
2 つ目の Entry
には Trigger
コレクションの一部として DataTrigger
も含まれていることがわかります。 これにより、3 番目の Entry
に何かが入力されるまで Entry
は無効になります。 iOS、Android、ユニバーサル Windows プラットフォーム (UWP) で実行されている起動時のページを次に示します。
現在の表示状態は "Disabled" であるため、iOS 画面と Android 画面では 2 番目の Entry
の背景がピンク色になります。 Entry
の UWP の実装では、Entry
が無効になっている場合に背景色を設定できません。
3 番目の Entry
になんらかのテキストを入力すると、2 番目の Entry
が "Normal" 状態に切り替わり、背景はライムになります。
2 つ目の Entry
にタッチすると、入力フォーカスが取得されます。 "Focused" 状態に切り替えられて、高さが 2 倍に拡張されます。
入力フォーカスを取得すると、Entry
のライムの背景は保持されないことがわかります。 Visual State Manager によって表示状態が切り替わると、前の状態によって設定されたプロパティは設定解除されます。 表示状態は相互に排他的であることに注意してください。 "Normal" 状態は、単に Entry
が有効であるということを意味しません。 これは、Entry
が有効で、入力フォーカスがないことを意味します。
Entry
が "Focused" 状態のときに背景をライムに設定する場合は、その表示状態に別の 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 マークアップ
多くの場合、2 つ以上のビュー間で同じ Visual State Manager マークアップを共有する必要があります。 この場合は、マークアップを 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>
これらのタグ内には、1 つ以上の 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 in Style ページの完全な 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
ビューが、表示状態に対して同じように応答するようになりました。 また、"Focused" 状態には、入力フォーカスがある場合にも、各 Entry
がライムの背景になるように 2 つ目の Setter
が含められたことにも注意してください。
Xamarin.Forms の表示状態
次の表に、Xamarin.Forms で定義されている表示状態を示します。
クラス | 状態 | その他の情報 |
---|---|---|
Button |
Pressed |
ボタンの表示状態 |
CheckBox |
IsChecked |
CheckBox の表示状態 |
CarouselView |
CarouselView の表示状態 | |
ImageButton |
Pressed |
ImageButton の表示状態 |
RadioButton |
$ | RadioButton の表示状態 |
Switch |
$ | 表示状態を切り替える |
VisualElement |
一般的な状態 |
これらの各状態には、CommonStates
という名前の表示状態グループを使用してアクセスできます。
さらに、CollectionView
は Selected
状態を実装します。 詳細については、「選択した項目の色を変更する」を参照してください。
複数の要素に状態を設定する
前の例では、視覚的な状態は 1 つの要素にアタッチされ、操作されていました。 ただし、1 つの要素にアタッチされているものの、同じスコープ内の他の要素にプロパティを設定する表示状態を作成することもできます。 これにより、状態が動作する各要素で表示状態を繰り返す必要がなくなります。
Setter
型には、表示状態に対して Setter
が操作するターゲット要素を表す string
型の TargetName
プロパティがあります。 TargetName
プロパティが定義されている場合、Setter
は、TargetName
に定義された要素の Property
を Value
に設定します。
<Setter TargetName="label"
Property="Label.TextColor"
Value="Red" />
この例では、label
の名前付きの Label
は TextColor
プロパティを Red
に設定します。 TargetName
プロパティを設定するときは、プロパティへの完全なパスを Property
で指定する必要があります。 したがって、TextColor
プロパティを Label
で設定するには、Property
は、Label.TextColor
のように指定します。
Note
Setter
オブジェクトによって参照されるすべてのプロパティは、バインド可能なプロパティによってサポートされている必要があります。
サンプルのセッター TargetName の VSM ページは、1 つの表示状態グループから複数の要素に状態を設定する方法を示しています。 XAML ファイルは、Label
要素、Entry
、Button
を含む StackLayout
で構成されます。
<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" という名前の相互に排他的な 2 つの状態があり、各状態には VisualState
タグが含まれています。
"Normal" 状態は、Button
が押されていないときにアクティブになり、質問への応答を入力できます。
Button
が押されると、"Pressed" 状態がアクティブになります。
"Pressed" の VisualState
は、Button
が押されると、その Scale
プロパティが既定値の 1 から 0.8 に変更されることを指定します。 さらに、entry
と名付けられた Entry
は、その Text
プロパティをパリに設定します。 したがって、結果として、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
から派生するすべてのクラスに基づいてすべてのオブジェクトに対して呼び出されるため、Visual State Manager を任意の VisualElement
オブジェクトとともに使用して、これらの変更に応答できます。
興味深いことに、表示状態グループ "CommonStates" の名前は、VisualElement
で明示的には参照されていません。 グループ名は、Visual State Manager の API の一部ではありません。 これまでに示した 2 つのサンプル プログラムの 1 つで、グループの名前を "CommonStates" から他のものに変更しても、プログラムは引き続き機能します。 グループ名は、そのグループ内の状態の一般的な説明にすぎません。 どのグループの表示状態も相互に排他的であることが暗黙的に理解されています。現在の状態になれるのは、常に 1 つの状態だけです。
独自の表示状態を実装する場合は、コードから VisualStateManager.GoToState
を呼び出す必要があります。 ほとんどの場合、この呼び出しはページ クラスの分離コード ファイルから行います。
サンプルの VSM 検証ページは、入力検証に関連して Visual State Manager を使用する方法を示しています。 XAML ファイルは、2 つの Label
要素、Entry
、Button
を含む StackLayout
で構成されます。
<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" という名前の相互に排他的な 2 つの状態があり、各状態には VisualState
タグが含まれています。
Entry
に有効な電話番号が含まれていない場合、現在の状態は "Invalid" であるため、Entry
にピンク色の背景が表示され、2 つ目の Label
が表示され、Button
は無効になります。
有効な電話番号を入力すると、現在の状態は "Valid" になります。 Entry
はライムの背景になり、2 番目の Label
が消え、Button
が有効になります。
分離コード ファイルは、Entry
から TextChanged
イベントを処理する責任があります。 ハンドラーは正規表現を使用して、入力文字列が有効かどうかを判断します。 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 を使用する利点は、すべての UI デザインを 1 つの場所に保持する XAML で、ビジュアル要素が異なる状態に反応する方法を一括して制御できることです。 これにより、分離コードから直接ビジュアル要素にアクセスして、視覚的な外観を設定する必要がなくなります。
表示状態トリガー
表示状態は、VisualState
が適用される条件を定義する特殊なトリガーのグループである、状態トリガーをサポートします。
状態トリガーは、VisualState
の StateTriggers
コレクションに追加されます。 このコレクションには、1 つの状態トリガーを含めることも、複数の状態トリガーを含めることもできます。 コレクション内のいずれかの状態トリガーがアクティブになっていると、VisualState
が適用されます。
状態トリガーを使用してビジュアルの状態を制御する場合、Xamarin.Forms では、アクティブにするトリガー (および対応する VisualState
) を決定するために、次の優先順位規則が使用されます。
StateTriggerBase
から派生したトリガー。MinWindowWidth
条件の適用によってアクティブにされたAdaptiveTrigger
。MinWindowHeight
条件の適用によってアクティブにされたAdaptiveTrigger
。
複数のトリガーが同時にアクティブにされた場合 (たとえば、2 つのカスタム トリガー)、マークアップで最初に宣言されたトリガーが優先されます。
状態トリガーの詳細については、「状態トリガー」を参照してください。
アダプティブ レイアウトに Visual State Manager を使用する
通常、電話で実行される Xamarin.Forms アプリケーションは、縦または横の縦横比で表示され、デスクトップで実行される Xamarin.Forms プログラムは、さまざまなサイズと縦横比になるようにサイズが変更されます。 適切に設計されたアプリケーションでは、これらのさまざまなページまたはウィンドウのフォーム ファクターに対して、コンテンツが異なる方法で表示される場合があります。
この手法は、アダプティブ レイアウトと呼ばれることもあります。 アダプティブ レイアウトにはプログラムのビジュアルのみが関連するため、Visual State Manager は理想的なアプリケーションです。
簡単な例は、アプリケーションのコンテンツに影響を与えるボタンの小さなコレクションを表示するアプリケーションです。 縦モードでは、これらのボタンはページの上部の水平行に表示される場合があります。
横モードでは、ボタンの配列が片側に移動されて、列として表示されることがあります。
上から順に、ユニバーサル Windows プラットフォーム、Android、iOS でプログラムが実行されています。
サンプルの VSM アダプティブ レイアウト ページでは、"OrientationStates" という名前のグループが定義され、"Portrait" と "Landscape" という名前の 2 つの表示状態が定義されています。 (より複雑なアプローチでは、複数の異なるページまたはウィンドウの幅に基づいている場合があります)。
VSM マークアップは、XAML ファイル内の 4 つの場所で発生します。 mainStack
という名前の StackLayout
には、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>
内側の menuScroll
という名前の ScrollView
と、menuStack
という名前の StackLayout
は、ボタンのメニューを実装します。 これらのレイアウトの向きは mainStack
の反対です。 メニューは縦モードでは水平、横モードでは垂直にする必要があります。
VSM マークアップの 4 番目のセクションは、ボタン自体の暗黙的なスタイルです。 このマークアップは、縦向きおよび横向きに固有の VerticalOptions
、HorizontalOptions
、Margin
プロパティを設定します。
分離コード ファイルは、Button
コマンドを実装する menuStack
の BindingContext
プロパティを設定し、また、ページの 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
ハンドラーは、StackLayout
と ScrollView
の 2 つの要素に対して VisualStateManager.GoToState
を呼び出し、Button
要素に対して VisualStateManager.GoToState
を呼び出すために menuStack
の子をループ処理します。
XAML ファイル内の要素のプロパティを設定することで、分離コード ファイルで方向の変更をより直接処理できるように見えるかもしれませんが、Visual State Manager は、より構造化されたアプローチです。 すべてのビジュアルが XAML ファイルに保持されており、確認、保守、変更が容易になります。
Xamarin.University を使用した Visual State Manager
Xamarin.Forms 3.0 Visual State Manager のビデオ