Xamarin.Forms FlyoutPageをPushModalAsyncした後の画面で使ってみた

最初に

公式ドキュメントでは、FlyoutPageはルートページで使用することを前提としているとの記載があります。
そのため、ここで記載する方法により予期せぬ動作を起こす可能性があります。

一覧画面で選択した項目をFlyoutPageで作りたかった

現在作成しているアプリの画面構成の中に、一覧画面で選択した項目に対して複数の操作を行いたい画面が存在しました。
そして、その複数の操作について、明確な順番を設けずに、ユーザーが自由に各操作画面を行き来できるようにしたいと考えていました。

そのため、一覧画面からの遷移後にMenuが横から出てくる画面を作りたいなと考えました。
そして、そのような画面を作る方法としてXamarinにはFlyoutPageがあったため、試してみようと考えました。

公式ではルートページで使用するとあるが、、、

公式では、ルートページで使うようにと記載されているが、画面遷移後のページで使ってしまって大丈夫なのでしょうか。
ということで、実際にやってみました。

まず、画面遷移後に表示する画面についてです。
画面遷移後に表示する画面をPushModalAsyncを使用して、モーダルページとして画面表示をした場合、ModalStack、NavigationStackともに、カウントが0となっています。
f:id:b-kimagure:20210725213902p:plain:w500
これはもしかして、PushModalAsyncで画面表示した場合はルートページになるのでは?

この仮説を基に、サンプルコードを書いてみることにしました。
(といっても、ほぼVisual Studioが自動生成したコードのみですが。。。)

  • 最初に表示する画面

画面にボタンを配置しています。
そして、ボタンクリックイベントで、以下の通りPushModalAsyncを使用した画面遷移を行っています。

    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void Button_Clicked(object sender, EventArgs e)
        {
            Navigation.PushModalAsync(new NavigationPage(new ModalPageFlyout()));
        }
    }
  • FlyoutPage画面

VisualStudioで追加したFlyoutPageをほぼそのまま使用しています。
ただし、モーダルページとしてFlyoutPage画面を表示したため、元の画面に戻るための機能の追加を行っています。

戻る機能は、横から出てくるMenuに"Exit"として追加しています。
追加を行うためにまずは、自動生成されるXXXFlyout.xaml.csファイルの修正を行います。
自動生成されたXXXFlyout.xaml.csファイルには、インナークラスとしてViewModelが定義されており、そのコンストラクタで横から出てくるメニューに表示するアイテムを設定しています。
そのため、コンストラクタで定義しているメニュー項目に"Exit"を追加します。

    public partial class ModalPageFlyoutFlyout : ContentPage
    {
        public ListView ListView;

        public ModalPageFlyoutFlyout()
        {
            InitializeComponent();

            BindingContext = new ModalPageFlyoutFlyoutViewModel();
            ListView = MenuItemsListView;
        }

        class ModalPageFlyoutFlyoutViewModel : INotifyPropertyChanged
        {
            public ObservableCollection<ModalPageFlyoutFlyoutMenuItem> MenuItems { get; set; }

            public ModalPageFlyoutFlyoutViewModel()
            {
                MenuItems = new ObservableCollection<ModalPageFlyoutFlyoutMenuItem>(new[]
                {
                    new ModalPageFlyoutFlyoutMenuItem { Id = 0, Title = "Page 1", TargetType = typeof(ModalPageFlyoutDetail) },
                    new ModalPageFlyoutFlyoutMenuItem { Id = 1, Title = "Page 2", TargetType = typeof(ModalPageFlyoutDetail) },
                    new ModalPageFlyoutFlyoutMenuItem { Id = 2, Title = "Page 3", TargetType = typeof(ModalPageDetail2) },
                    new ModalPageFlyoutFlyoutMenuItem { Id = 3, Title = "Page 4", TargetType = typeof(ModalPageFlyoutDetail) },
                    new ModalPageFlyoutFlyoutMenuItem { Id = 4, Title = "Page 5", TargetType = typeof(ModalPageDetail2) },
                    new ModalPageFlyoutFlyoutMenuItem { Id = 5, Title = "Exit" },
                });
            }

            #region INotifyPropertyChanged Implementation
            public event PropertyChangedEventHandler PropertyChanged;
            void OnPropertyChanged([CallerMemberName] string propertyName = "")
            {
                if (PropertyChanged == null)
                    return;

                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
            #endregion
        }
    }

次に、XXXFlyout.xamlとXXXDetail.xamlが乗っかる画面の修正をします。
VisualStudioで自動生成したこの画面では、メニュー選択時の動作が実装されています。
そのため、"Exit"メニューが選択された場合の動作をメニュー選択時の動作の中に追加します。

今回は、id=5の項目をExitとしたため、メニューで選択した項目のid=5の場合にPopModalAsyncで画面を閉じています。
そして、そのままreturnすることで、以降の画面選択処理を実行せずに処理を終了させています。

    public partial class ModalPageFlyout : FlyoutPage
    {
        public ModalPageFlyout()
        {
            InitializeComponent();
            FlyoutPage.ListView.ItemSelected += ListView_ItemSelected;
        }

        private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            var item = e.SelectedItem as ModalPageFlyoutFlyoutMenuItem;
            if (item == null)
                return;

            if (item.Id == 5)
            {
                Navigation.PopModalAsync();
                return;
            }

            var page = (Page)Activator.CreateInstance(item.TargetType);

            page.Title = item.Title;

            Detail = new NavigationPage(page);
            IsPresented = false;

            FlyoutPage.ListView.SelectedItem = null;
        }
    }

やってみた結果

やってみた結果、iOSAndroidともにそれっぽく動かすことができました。

最後に

とりあえず、動くことは確認できました。
しかし、公式ドキュメントでは、ルートページで使うことが前提となっています。
(アプリのルートページとは記載されていないようでした)

そのため、この実装方法が本当に問題ないかはもう少し調べてみる必要があるかもしれません。