Xamarin.Forms(XAML) を使って、 NavigationPage + TabbedPage を作ってみます。
Twitter for iPhone や TweetBot みたいな感じの UI ですね。
ただ、
NavigationPage |- TabbedPage
といった構成はよく見かけるものの、その逆は見かけません。
また、上のような構成は、非推奨となっているようです。
たとえば、ほとんどのプラットフォームでは NavigationPage の子ページとして TabbedPage を追加しないよう推奨されます。 https://msdn.microsoft.com/ja-jp/magazine/dn904669.aspx
そこで、その逆、 TabbedPage
の子ページとして NavigationPage
を追加してみます。
単純に書くとしたら、下のような XAML になるはず。
MainPage.xaml
<TabbedPage x:Class="Mikazuki.MainPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Mikazuki"> <TabbedPage.Children> <local:NavPage /> </TabbedPage.Children> </TabbedPage>
NavPage.xaml
<NavigationPage x:Class="Mikazuki.NavPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> <Label Text="Hello!" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" /> </NavigationPage>
ただ、こうすると、
InvalidOperationException: NavigationPage must have a root Page before being used. Either call PushAsync with a valid Page, or pass a Page to the constructor before usage.
と、例外が発生してしまいます。
Root ページを設定しろと怒られているので、 NavigationPage(Page)
を呼び出す必要があります。
NavigationPage(Xamarin.Forms.Page) - Xamarin
ということで、書き換えます。
MainPage.xaml
<TabbedPage x:Class="Mikazuki.MainPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Mikazuki"> <TabbedPage.Children> <local:NavPage> <x:Arguments> <Label Text="Hello!" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" /> </x:Arguments> </local:NavPage> </TabbedPage.Children> </TabbedPage>
NavPage.xaml のコンテンツを、 x:Arguments
の子供として記述します。
NavPage.xaml
<NavigationPage x:Class="Mikazuki.NavPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> <x:Arguments /> </NavigationPage>
NavPage.xaml.cs
using Xamarin.Forms; namespace Natsuneko { public partial class NavPage : NavigationPage { public NavPage(Page page) : base(page) { InitializeComponent(); } } }
うえで示した、コンストラクタに対して、 MainPage.xaml のコンテンツを渡します。
こうすることで、TabbedPage
の子として NavigationPage
を追加できました。
でも、このままの状態だと、 MainPage.xaml が非常に大きくなります。
それはあれですので、多少使いやすいように改造します。
構造的にはこんな感じ。
MainPage (TabbedPage) |- NavPage (NavigationPage) | |- FirstPage (ContentPage) | |- ...
MainPage.xaml
<TabbedPage x:Class="Mikazuki.MainPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Mikazuki"> <TabbedPage.Children> <local:NavPage> <x:Arguments> <local:FirstPage /> </x:Arguments> </local:NavPage> </TabbedPage.Children> </TabbedPage>
NavPage.xaml
<NavigationPage x:Class="Mikazuki.NavPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> <x:Arguments /> </NavigationPage>
これは、さっきのものと変わりません。
NavPage.xaml.cs
using Xamarin.Forms; namespace Mikazuki { public partial class NavPage : NavigationPage { public NavPage(Page page) : base(page) { InitializeComponent(); Title = page.Title; Icon = page.Icon; } } }
親である TabbedPage にアイコンとタブを表示するために、
Title
と Icon
を設定しておきます。
FirstPage.xaml
<ContentPage x:Class="Mikazuki.FirstPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> <Label Text="Hello!" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" /> </ContentPage>
これで、やりたいことを達成することができました。
あとは、前回書いた NavigationService
を、いい感じに切り替えることで、
自然に使えるのではないかと思っています。
参考
2015/11/10 追記
BindingContext
を使ってどうのこうのする場合は、
page.BindingContextChanged += (sender, args) => { var viewModel = page.BindingContext as HogeViewModelBase; // Title, Icon プロパティがある。 if (viewModel == null) return; Title = viewModel.Title; Icon = viewModel.Icon; };
みたいなものを、 NavBar.xaml.cs のコンストラクタに追加すると幸せになれる気がします。