Xamarin.Forms Visual State Manager

Download Sampleサンプルのダウンロード

コードから設定された表示状態に基づいて 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 という名前のプロパティを定義します。 StatesVisualStateGroups のコンテンツのプロパティであるため、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 オブジェクトです。

SettersVisualState のコンテンツのプロパティではないため、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 オブジェクトによって参照されるすべてのプロパティは、バインド可能なプロパティによってサポートされている必要があります。

このようなマークアップは、VsmDemos サンプル プログラムの VSM on View ページの基礎です。 このページには 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) で実行されている起動時のページを次に示します。

VSM on View: Disabled

現在の表示状態は "Disabled" であるため、iOS 画面と Android 画面では 2 番目の Entry の背景がピンク色になります。 Entry の UWP の実装では、Entry が無効になっている場合に背景色を設定できません。

3 番目の Entry になんらかのテキストを入力すると、2 番目の Entry が "Normal" 状態に切り替わり、背景はライムになります。

VSM on View: Normal

2 つ目の Entry にタッチすると、入力フォーカスが取得されます。 "Focused" 状態に切り替えられて、高さが 2 倍に拡張されます。

VSM on View: Focused

入力フォーカスを取得すると、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 オブジェクトを適切に機能させるには、そのグループ内のすべての状態に対して、VisualStateGroupVisualState オブジェクトを含める必要があります。 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.VisualStateGroupsSetter タグを追加します。

<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 が含められたことにも注意してください。

VSM in Style

Xamarin.Forms の表示状態

次の表に、Xamarin.Forms で定義されている表示状態を示します。

クラス 状態 その他の情報
Button Pressed ボタンの表示状態
CheckBox IsChecked CheckBox の表示状態
CarouselView DefaultItem, CurrentItem, PreviousItem, NextItem CarouselView の表示状態
ImageButton Pressed ImageButton の表示状態
RadioButton CheckedUnchecked RadioButton の表示状態
Switch OnOff 表示状態を切り替える
VisualElement Normal, Disabled, Focused, Selected 一般的な状態

これらの各状態には、CommonStates という名前の表示状態グループを使用してアクセスできます。

さらに、CollectionViewSelected 状態を実装します。 詳細については、「選択した項目の色を変更する」を参照してください。

複数の要素に状態を設定する

前の例では、視覚的な状態は 1 つの要素にアタッチされ、操作されていました。 ただし、1 つの要素にアタッチされているものの、同じスコープ内の他の要素にプロパティを設定する表示状態を作成することもできます。 これにより、状態が動作する各要素で表示状態を繰り返す必要がなくなります。

Setter 型には、表示状態に対して Setter が操作するターゲット要素を表す string 型の TargetName プロパティがあります。 TargetName プロパティが定義されている場合、Setter は、TargetName に定義された要素の PropertyValue に設定します。

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

この例では、label の名前付きの LabelTextColor プロパティを Red に設定します。 TargetName プロパティを設定するときは、プロパティへの完全なパスを Property で指定する必要があります。 したがって、TextColor プロパティを Label で設定するには、Property は、Label.TextColor のように指定します。

Note

Setter オブジェクトによって参照されるすべてのプロパティは、バインド可能なプロパティによってサポートされている必要があります。

VsmDemos サンプルの VSM with Setter TargetName ページは、1 つの表示状態グループから複数の要素に状態を設定する方法を示しています。 XAML ファイルは、Label 要素、EntryButton を含む 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 が押されていないときにアクティブになり、質問への応答を入力できます。

VSM Setter TargetName: Normal State

Button が押されると、"Pressed" 状態がアクティブになります。

VSM Setter TargetName: Pressed State

"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 を呼び出す必要があります。 ほとんどの場合、この呼び出しはページ クラスの分離コード ファイルから行います。

VsmDemos サンプルの VSM Validation ページは、入力検証に関連して Visual State Manager を使用する方法を示しています。 XAML ファイルは、2 つの Label 要素、EntryButton を含む 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 は無効になります。

VSM Validation: Invalid State

有効な電話番号を入力すると、現在の状態は "Valid" になります。 Entry はライムの背景になり、2 番目の Label が消え、Button が有効になります。

VSM Validation: Valid State

分離コード ファイルは、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 が適用される条件を定義する特殊なトリガーのグループである、状態トリガーをサポートします。

状態トリガーは、VisualStateStateTriggers コレクションに追加されます。 このコレクションには、1 つの状態トリガーを含めることも、複数の状態トリガーを含めることもできます。 コレクション内のいずれかの状態トリガーがアクティブになっていると、VisualState が適用されます。

状態トリガーを使用してビジュアルの状態を制御する場合、Xamarin.Forms では、アクティブにするトリガー (および対応する VisualState) を決定するために、次の優先順位規則が使用されます。

  1. StateTriggerBase から派生したトリガー。
  2. MinWindowWidth 条件の適用によってアクティブにされた AdaptiveTrigger
  3. MinWindowHeight 条件の適用によってアクティブにされた AdaptiveTrigger

複数のトリガーが同時にアクティブにされた場合 (たとえば、2 つのカスタム トリガー)、マークアップで最初に宣言されたトリガーが優先されます。

状態トリガーの詳細については、「状態トリガー」を参照してください。

アダプティブ レイアウトに Visual State Manager を使用する

通常、電話で実行される Xamarin.Forms アプリケーションは、縦または横の縦横比で表示され、デスクトップで実行される Xamarin.Forms プログラムは、さまざまなサイズと縦横比になるようにサイズが変更されます。 適切に設計されたアプリケーションでは、これらのさまざまなページまたはウィンドウのフォーム ファクターに対して、コンテンツが異なる方法で表示される場合があります。

この手法は、アダプティブ レイアウトと呼ばれることもあります。 アダプティブ レイアウトにはプログラムのビジュアルのみが関連するため、Visual State Manager は理想的なアプリケーションです。

簡単な例は、アプリケーションのコンテンツに影響を与えるボタンの小さなコレクションを表示するアプリケーションです。 縦モードでは、これらのボタンはページの上部の水平行に表示される場合があります。

VSM Adaptive Layout: Portrait

横モードでは、ボタンの配列が片側に移動されて、列として表示されることがあります。

VSM Adaptive Layout: Landscape

上から順に、ユニバーサル Windows プラットフォーム、Android、iOS でプログラムが実行されています。

VsmDemos サンプルの VSM Adaptive Layout ページでは、"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 番目のセクションは、ボタン自体の暗黙的なスタイルです。 このマークアップは、縦向きおよび横向きに固有の VerticalOptionsHorizontalOptionsMargin プロパティを設定します。

分離コード ファイルは、Button コマンドを実装する menuStackBindingContext プロパティを設定し、また、ページの 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 ハンドラーは、StackLayoutScrollView の 2 つの要素に対して VisualStateManager.GoToState を呼び出し、Button 要素に対して VisualStateManager.GoToState を呼び出すために menuStack の子をループ処理します。

XAML ファイル内の要素のプロパティを設定することで、分離コード ファイルで方向の変更をより直接処理できるように見えるかもしれませんが、Visual State Manager は、より構造化されたアプローチです。 すべてのビジュアルが XAML ファイルに保持されており、確認、保守、変更が容易になります。

Xamarin.University を使用した Visual State Manager

Xamarin.Forms 3.0 Visual State Manager のビデオ