实现 ReactUI.XamForms 母版详细信息页的模式



我目前正在Xamarin Forms项目中工作,并尝试将ReactiveUI与Master Detail Page导航标准Xamarin Forms项目一起使用。

在阅读了ReactiveUI网站上的文档以及Kent Boogaart的"You,I and ReactiveUI"一书之后,我仍然迷失了如何使Master Detail设置与ReactiveUI和Xamarin Forms一起使用以进行应用程序导航。

我正在引导应用程序,它加载根视图(主详细信息页面(:

internal sealed class AppBootstrap: ReactiveObject, IScreen
{
public AppBootstrap()
{
RegisterDependencies();
this
.Router
.NavigateAndReset
.Execute(GetRootViewModel())
.Subscribe();
}
public RoutingState Router { get; } = new RoutingState();
public Page GetMainPage() => new RoutedViewHost();
private void RegisterDependencies()
{
Locator.CurrentMutable.RegisterConstant(this, typeof(IScreen));
/*View Model Registrations*/
Locator.CurrentMutable.Register(() => new RootView(), typeof(IViewFor<RootViewModel>));
Locator.CurrentMutable.Register(()=> new MenuView(), typeof(IViewFor<MenuViewModel>));
Locator.CurrentMutable.Register(()=> new DetailView(), typeof(IViewFor<DetailViewModel>));
/*Service Registrations*/
}
private IRoutableViewModel GetRootViewModel()
{
//Add check for login here
return new RootViewModel(this);
}
}

然后在我的RootViewModel中:

public sealed class RootViewModel: ViewModelBase
{
private Page _masterPage;
private Page _detailPage;
public RootViewModel(IScreen hostScreen) : base(hostScreen)
{ }
public Page MasterPage
{
get => _masterPage;
set => this.RaiseAndSetIfChanged(ref _masterPage, value);
}
public Page DetailPage
{
get => _detailPage;
set => this.RaiseAndSetIfChanged(ref _detailPage, value);
}
public override Action<CompositeDisposable> OnActivated()
{
//What do I do?
}
}

在谷歌搜索和搜索堆栈溢出之后,这里似乎没有答案,"文档"也不是太有用。ReactiveUI 站点上列出的示例以及所有在线博客文章都只处理单页导航;副 大多数现实世界的手机应用程序实现的基于抽屉的更标准的导航。

任何关于该做什么的帮助或链接到实际工作的基于母版详细信息页面的导航都将非常有帮助,因为我刚刚尝试找到任何实质性的东西都失败了。

你说的没错,缺乏MasterDetailPage的文档。感谢您在网站上创建该问题。在我们更新之前,这里有一种方法可以做到这一点。

在此示例中,我假设详细信息页面对每个项目具有完全相同的布局。即使这不适合您的用例,也很容易对其进行自定义以使其适用于各种布局。

我们不是在每次选择项目时都创建新的 DetailViewModel 和 DetailPage,而是简单地交换模型。视图侦听此更改并重新绑定。

public class MyMasterDetailViewModel : ReactiveObject, IRoutableViewModel
{
private IScreen _hostScreen;
public MyMasterDetailViewModel(IScreen hostScreen = null)
{
_hostScreen = hostScreen ?? Locator.Current.GetService<IScreen>();
var cellVms = GetData().Select(model => new CustomCellViewModel(model));
MyList = new ObservableCollection<CustomCellViewModel>(cellVms);
// Set the first list item as the default detail view content.
Detail = new DetailViewModel();
Detail.Model = cellVms.First().Model;
// Swap out the detail's model property every time the user selects an item.
this.WhenAnyValue(x => x.Selected)
.Where(x => x != null)
.Subscribe(cellVm => Detail.Model = cellVm.Model);
}
private CustomCellViewModel _selected;
public CustomCellViewModel Selected
{
get => _selected;
set => this.RaiseAndSetIfChanged(ref _selected, value);
}
public DetailViewModel Detail { get; }
public ObservableCollection<CustomCellViewModel> MyList { get; }
}

public partial class MyMasterDetailPage : ReactiveMasterDetailPage<MyMasterDetailViewModel>
{
public MyMasterDetailPage()
{
InitializeComponent();
ViewModel = new MasterDetailViewModel();
Detail = new NavigationPage(new DetailPage(ViewModel.Detail));
this.WhenActivated(
disposables =>
{
this
.OneWayBind(ViewModel, vm => vm.MyList, v => v.MyListView.ItemsSource)
.DisposeWith(disposables);
this
.Bind(ViewModel, vm => vm.Selected, v => v.MyListView.SelectedItem)
.DisposeWith(disposables);
this
.WhenAnyValue(x => x.ViewModel.Selected)
.Where(x => x != null)
.Subscribe(
model =>
{
// Hide the master list every time the user selects an item
// and reset the SelectedItem "trigger."
MyListView.SelectedItem = null;
IsPresented = false;
})
.DisposeWith(disposables);
});
}
}

public class DetailViewModel : ReactiveObject
{
private CustomData _model;
public CustomData Model
{
get => _model;
set => this.RaiseAndSetIfChanged(ref _model, value);
}
public string Title => Model.Title;
}

public partial class DetailPage : ReactiveContentPage<DetailViewModel>
{
public DetailPage(DetailViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
this.WhenActivated(
disposables =>
{
this
.WhenAnyValue(x => x.ViewModel.Model)
.Where(x => x != null)
.Subscribe(model => PopulateFromModel(model))
.DisposeWith(disposables);
});
}
private void PopulateFromModel(MyModel model)
{
Title = model.Title;
TitleLabel.Text = model.Title;
}
}

<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveMasterDetailPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:local="clr-namespace:XamFormsSandbox"
x:Class="XamFormsSandbox.MyMasterDetailPage"
x:TypeArguments="local:MyMasterDetailViewModel"
NavigationPage.HasNavigationBar="False">
<MasterDetailPage.Master>
<ContentPage Title="Master">
<StackLayout>
<ListView x:Name="MyListView">
<ListView.ItemTemplate>
<DataTemplate>
<local:CustomCell />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
</MasterDetailPage.Master>
</rxui:ReactiveMasterDetailPage>

请注意,我在上面的 XAML 中隐藏了带有NavigationPage.HasNavigationBar="False"的路由视图主机导航栏。否则,我们将有两个导航栏彼此重叠。

更新

下面是示例项目(当前为 PR(的链接: https://github.com/reactiveui/ReactiveUI/pull/1741

最新更新