Xamarin.FormsでのMasterDetailとバックボタン

  • 16
    いいね
  • 0
    コメント

de:code2017アプリとかで気になってた事があって

MasterDetail組む時にホーム画面への配慮した方が良いかも。っていう問題。

iOSだと意識しなくて良いのだけどAndroidだとバックボタンがあるのでホーム画面に配慮する必要があると思っています。

例えばGoogle MapであったりGmailアプリであったりで、ハンバーガーメニューで他の画面へ移動してからバックスペースを押すと最初の画面へ戻ると思います。

こうなっていないアプリって結構めんどくさくて

例えば設定画面を開いて設定を変更して元の画面に戻る、みたいなケースを想定した時に

ホーム画面を考慮していないアプリの場合

ホーム画面 -> メニューを開く -> 設定画面へ移動 -> 設定操作 -> メニューを開く -> ホームへ戻る

というオペレーションになるのだけど

ホーム画面 -> メニューを開く -> 設定画面へ移動 -> 設定操作 -> バックボタン

っていう操作をしてアプリが閉じちゃう、みたいな事になったりならなかったり。

Xamarin.FormsでCustomRendererを使わずにMasterDetailをホーム画面を意識した感じにはいくつか方法はあるけど、MasterDetailっぽい動作を維持しつつ(開いてるメニューが閉じてDetailが変わる感じ)実現しようとすると、DetailにNavigationPageを二つ重ねる感じで実現できます。

VS2017のXamarinテンプレートのMasterDetailを今回の説明方法で修正したイメージはこんな感じです。

https://sleepyandhungry1984.tumblr.com/post/162319590522/xamarinforms-masterdetail

MasterDetail
Master -- ContentPage
Detai -- NavigationPage(outer)
       ----NavigationPage(inner)
           ----ContentPage(Home)

みたいな感じで構成します。

そして重要な点はinnerナビゲーションの下に追加するページはNavigationPage.SetHasNavigationBarでナビゲーションバーを表示しないようにしておきます。

これでメニューから例えばAboutページを選択した場合にはInnerにPushAsyncして

NavigationPage(outer)
-------NavigationPage(inner)
-------------ContentPage(Home)
-------------ContentPage(About)

という感じにします。

こうするとハンバーガーメニューは残ったままDetailが切り替わります。

でAndroidのバックボタンを押すとAboutがpopしてHomeに戻ると。

Aboutが開いている状態でメニューからHomeを選択した場合にはPopToRootしてあげれば良くて、Aboutが開いている状態で別のメニューを選んだ場合には InnerにPushAsyncした後にAboutをRemovePageする感じでOK。

で、ページ遷移させようとした場合には

NavigationPage(outer)
-------NavigationPage(inner)
-------------ContentPage(Home)
-------ContentPage(Newitem)

という感じで、outerにPushAsyncすることでNavigationBarのハンバーガメニューが ←矢印に変わってナビゲーションする感じです。

メンドクサイし実装が複雑化するしMVVMフレームワークとかだとできなかったりする場合もあるし、Xamarin.Formsでこの辺にコストをかけるのか?っていう疑問もあるんですけど、でも出来てると良いかも?

そんなお話でした。

コードをちょっと張り付けておきます。

#region

using System;
using System.Threading;
using MasterDetail.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

#endregion

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]

namespace MasterDetail
{
    // XamlになれるとC#コードでも普通に画面書けることに気が付いた。
    // Intellisenseとコード補完最大限に効くので結構書きやすい。
    // 物凄く読みにくいけどね🍣

    public partial class App : Application
    {
        private readonly NavigationPage _innerNavPage;
        private readonly MasterDetailPage _masterDetailPage;
        private readonly NavigationPage _outerNavPage;

        public App()
        {
            InitializeComponent();

            // browse page
            var browsePage = new ItemsPage
            {
                Title = "Browse"
            };
            NavigationPage.SetHasNavigationBar(browsePage, false);

            // aboutPage
            var aboutPage = new AboutPage
            {
                Title = "About"
            };
            NavigationPage.SetHasNavigationBar(aboutPage, false);

            // InnerNavigationPage
            _innerNavPage = new NavigationPage(browsePage)
            {
                Title = browsePage.Title
            };
            _innerNavPage.PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == NavigationPage.CurrentPageProperty.PropertyName)
                    _innerNavPage.Title = _innerNavPage.CurrentPage.Title;
            };

            // OuterNavigationPage
            _outerNavPage = new NavigationPage(_innerNavPage);

            // MenuPage
            Page menuPage = null;
            menuPage = new ContentPage
            {
                Title = "Menu",
                Content = new StackLayout
                {
                    Children =
                    {
                        new ContentView
                        {
                            Padding = 20,
                            Content = new Label {Text = "Browse"},
                            GestureRecognizers =
                            {
                                new TapGestureRecognizer
                                {
                                    Command = new Command(() => { MessagingCenter.Send(menuPage, "Menu", "Browse"); })
                                }
                            }
                        },
                        new ContentView
                        {
                            Padding = 20,
                            Content = new Label {Text = "About"},
                            GestureRecognizers =
                            {
                                new TapGestureRecognizer
                                {
                                    Command = new Command(() => { MessagingCenter.Send(menuPage, "Menu", "About"); })
                                }
                            }
                        }
                    }
                }
            };


            _masterDetailPage = new MasterDetailPage();
            _masterDetailPage.Master = menuPage;
            _masterDetailPage.Detail = _outerNavPage;

            MainPage = _masterDetailPage;

            MessagingCenter.Subscribe(this, "Navigation",
                new Action<ItemsPage, Page>((s, p) => { _outerNavPage.PushAsync(p); }));

            MessagingCenter.Subscribe(this, "Menu", new Action<Page, string>((p, s) =>
            {
                if (s == "Browse")
                {
                    if (!(_innerNavPage.CurrentPage is ItemsPage))
                        _innerNavPage.PopToRootAsync();
                }
                else if (s == "About")
                {
                    if (_innerNavPage.CurrentPage is AboutPage)
                        return;

                    if (_innerNavPage.CurrentPage is ItemsPage)
                    {
                        _innerNavPage.PushAsync(aboutPage);
                    }
                    else
                    {
                        var currentPage = _innerNavPage.CurrentPage;
                        _innerNavPage.PushAsync(aboutPage)
                            .ContinueWith(task =>
                            {
                                SynchronizationContext.Current.Post(state => { _innerNavPage.Navigation.RemovePage(currentPage); }, null);
                            });
                    }
                }
                _masterDetailPage.IsPresented = false;
            }));
        }
    }
}