Xamarin.Forms+ReactivePropertyでスマホアプリ開発!《MasterDetailPage編》
【記事ツリー】
概要
Xamarin.Forms+ReactivePropertyでスマホアプリ開発!《本編》において、どのようにMasterDetailPageを実装したのかといった話です。正直一番手こずったパートですので、本文も大ボリュームになります!ごめんなさい!
そもそもMasterDetailPageって?
アニメーションGIFで示すとこんな感じのページです。
(Xamarin.Forms で MasterDetailPage を使うには - Xamarin 日本語情報より引用)
つまり、
- 左上のボタンを押すとメニューページが出てくる
- メニューウィンドウの1つをタップするとそれに対応したページに切り替わる
といった構造になっているわけですね。ここで「メニューページ」「対応したページ」は<ContentPage>
(普通のページ)や<TabbedPage>
(タブで中身を切り替えられるページ)で記述しますが、両者を関連付けるページとして<MasterDetailPage>
を使用します。
どうやって実装するの?
戦略としてはこんな感じです。
- 「対応したページ」を1つ以上用意する。こちらは何も工夫する必要はありません
- 「メニューページ」を1つ用意する。例えば
<ListView>
コントロールを使用する場合、「リストの項目を押したか」「押したとしたらどの項目を押したか」を、イベント・プロパティ取得できるようにする必要があります - 「関連付けるページ」を1つ用意する。これにおける
Master
プロパティは「メニューページ」のインスタンスを持っており、Detail
プロパティは「対応したページ」のインスタンスを持っている。また、IsPresented
プロパティがfalseなら、「メニューページ」が引っ込んでいることを表す - 「関連付けるページ」は、「メニューページ」におけるイベントを監視し、「対応したページ」を切り替えたくなるようなイベントが来た場合、
Detail
プロパティとIsPresented
プロパティを更新する処理を行う
これを実装するため、最初参考にしたページでは、x:Name
を多用したコードになっていました。
Xamarin.Forms の MasterDetail を実装してみよう - Qiita
しかし、あまりそういったことをしたくなかったので、以下ではViewModelとReactivePropertyを活用した解決方法を考えます。
解決策
「関連付けるページ」での処理
Views/MasterDetailPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SampleApplication.Views"
x:Class="SampleApplication.Views.DetailPage">
<MasterDetailPage.Master>
<local:MasterPage/>
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage>
<x:Arguments>
<local:Page1/>
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
</MasterDetailPage>
<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SampleApplication.Views"
x:Class="SampleApplication.Views.DetailPage">
<MasterDetailPage.Master>
<local:MasterPage/>
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage>
<x:Arguments>
<local:Page1/>
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
</MasterDetailPage>
この辺は割とテンプレですね。ただ、これに結びついたcsを少し工夫しています。
using System;
using Xamarin.Forms;
using SampleApplication.ViewModels;
using Xamarin.Forms.Xaml;
namespace SampleApplication.Views
{
public partial class MasterDetailPage: MasterDetailPage
{
public MasterDetailPage()
{
InitializeComponent();
// 選択を切り替えた際の動きを記述する
var masterPage = this.Master as MasterPage;
var masterPageViewModel = masterPage.BindingContext as MasterPageViewModel;
masterPageViewModel.SelectedMenuItem.Subscribe(item => {
// アイテムがnullでなければ、それに合わせて表示するページを切り替える
if (item != null) {
// itemに登録したTargetTypeから表示ページのインスタンスを作成し、代入する
Detail = new NavigationPage((Page)Activator.CreateInstance(item.TargetType));
// 選択を解除する
masterPageViewModel.SelectedMenuItem.Value = null;
// 選択ページを引っ込める
IsPresented = false;
}
});
}
}
}
つまり、「メニューページ」を項目をタップした際に変わる項目名をSelectedMenuItem
プロパティとして、ReactivePropertyの変更通知機能により、その変化を読み取るわけです。これにより、「関連付けるページ」のXAMLに細工しなくてても、その子である「メニューページ」の変更を通知として拾って利用することができます。
また、こうして引っ張ってきたSelectedMenuItem
プロパティを叩くことで、「選択を解除する」操作も同様に実現できます。
本来ならSelectedMenuItem
プロパティのSubcribe
メソッドに登録したいところなのですが、「関連付けるページ」におけるMaster
プロパティとDetail
プロパティとIsPresented
プロパティを叩きやすくするため、コードビハインド部分に書くことになりました。
「メニューページ」での処理
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SampleApplication.Views.MasterPage"
Padding="0,40,0,0">
<StackLayout VerticalOptions="FillAndExpand">
<Label Text="メニュー" FontSize="Large" Margin="10,10,10,10"/>
<ListView VerticalOptions="FillAndExpand" SeparatorVisibility="None"
ItemsSource="{Binding MenuList}"
SelectedItem="{Binding SelectedMenuItem.Value}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Title}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
ここで注目したいのはメニュー部分です。Data Bindingを駆使することにより、リストを表示するだけでなく、変更通知も検出できるようになりました(前述の通り)。また、コードビハインドはシンプル極まりない仕様ですが、
using SampleApplication.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace SampleApplication.Views
{
public partial class MasterPage : ContentPage
{
public MasterPage()
{
InitializeComponent();
this.BindingContext = new MasterPageViewModel();
}
}
}
逆にViewModelは少しリッチになっています。View側での余計な負担を減らしたというわけですね。
using SampleApplication.Models;
using SampleApplication.Views;
using Reactive.Bindings;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace SampleApplication.ViewModels
{
class MasterPageViewModel : INotifyPropertyChanged
{
#pragma warning disable 0067
public event PropertyChangedEventHandler PropertyChanged;
// ReactiveProperty
public ReactiveProperty<MenuItem> SelectedMenuItem { get; set; }
= new ReactiveProperty<MenuItem>();
// ReactiveCollection
public ReadOnlyReactiveCollection<MenuItem> MenuList { get; }
// コンストラクタ
public MasterPageViewModel()
{
#region ReactiveCollectionを設定
// MenuList
{
var menuList = new List<MenuItem> {
new MenuItem {Title = "ページ1", TargetType = typeof(Page1) },
new MenuItem {Title = "ページ2", TargetType = typeof(Page2) },
new MenuItem {Title = "ページ3", TargetType = typeof(Page3) },
};
var oc = new ObservableCollection<MenuItem>(menuList);
MenuList = oc.ToReadOnlyReactiveCollection();
}
#endregion
}
}
}
using System;
namespace SampleApplication.Models
{
public class MenuItem
{
public string Title { get; set; }
public Type TargetType { get; set; }
}
}
課題
このようにすればx:Name
せずにMasterDetailPageを表現することができました。一応、「MasterPageViewModelのSelectedMenuItemのSubcribe
メソッドからメニュー関係のロジックを行うことでMasterDetailPageクラスの負担(コードビハインド)を減らす」ことも考えましたが、MasterプロパティとDetailプロパティをData Bindingすることが難しく、試行錯誤中です……。
Author And Source
この問題について(Xamarin.Forms+ReactivePropertyでスマホアプリ開発!《MasterDetailPage編》), 我々は、より多くの情報をここで見つけました https://qiita.com/YSRKEN/items/521d2962ac664fa95ebe著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .