Navigation导航机制
问题一:因为View调用ViewModel,ViewModel调用IService,是单向的,不能形成环,因此IService不能调用View。
但IContentPage还要导航view,因此将view剥离成页面键字符串string pageKey。
导航有两种:侧栏导航,页面中导航
页面间导航的方法:new一个ItemDetailPage再Navigation.PushAsync
async void OnItemSelected(object sender, EventArgs args)
{
var layout = (BindableObject)sender;
var item = (Item)layout.BindingContext;
await Navigation.PushAsync(new ItemDetailPage(new ItemDetailViewModel(item)));
}
问题二:照理说导航只能在View里做,但为了满足mvvm架构,应该在IService里做。如何让IService做View的事?
xamarin有一套机制能获得正在显示的view,而需要导航的页面正是正在显示的view。我们在页面上看到的其实只有MainPage页,所有的页面都在MainPage里,MasterDetail模板里MainPage页是一个MasterDetailPage,其他页是ContentPage。MainPage是整个项目页面的容器。
问题三:那么如何获得MainPage?
App中MainPage = new MainPage();
因此在ContentNavigationService里获得它
/// <summary>
/// MainPage.
/// </summary>
public MainPage MainPage =>
_mainPage ?? (_mainPage = Application.Current.MainPage as MainPage);
//将App的MainPage属性转换成MainPage实例赋给_mainPage
/// <summary>
/// MainPage.
/// </summary>
private MainPage _mainPage;
必须有NavigationPage才能导航,NavigationPage提供了导航机制。ItemsPage必须套在NavigationPage里才能实现导航。
问题四:如何确定导航到谁?
IContentNavigationService里定义一组常量,来定位页面
public static class ContentNavigationConstants
{
/// <summary>
/// 诗词详情页。
/// </summary>
public const string DetailPage = nameof(Views.DetailPage);
}
问题五:每次导航都要new一个目标ContentPage,会导致内存泄漏,怎么办?
缓存机制:一个Page只存一份,将new page的业务外包给新建的IContentPageActivationService来做。如果cache里有缓存项,就返回缓存项的值,如果没有则new一个新的ContentPage。
反射:根据类型直接创建对象
都有哪些类型? 在IContentNavigationService创建一个页面键-页面类型字典。
/// <summary>
/// 页面键-页面类型字典。
/// </summary>
public static readonly Dictionary<string, Type> PageKeyTypeDictionary =
new Dictionary<string, Type>
{
{DetailPage,typeof(DetailPage)}
};
/// <summary>
/// 激活内容页。
/// </summary>
/// <param name="pageKey">页面键。</param>
public ContentPage Activate(string pageKey)
{
//如果cache里有缓存项,就返回缓存项的值
if (cache.ContainsKey(pageKey))
{
return cache[pageKey];
}
//如果没有则new一个新的ContentPage
//创建给定类型的实例
cache[pageKey] = (ContentPage) Activator.CreateInstance(
ContentNavigationConstants.PageKeyTypeDictionary[pageKey]);
return cache[pageKey];
}
总结思路
ContentNavigationService:
1.获得MainPage
2.调用MainPage的Detail(NavigationPage)的PushAsync属性进行导航
3.因为导航需要目标页,因此将new page的业务外包给ContentPageActivationService
ContentPageActivationService:
1.检查有没有缓存,如果有则直接拿,
2.没有则通过反射创建,反射需要类型
3.类型在内容导航常量ContentNavigationConstants中的类型字典里存着
导航传参
问题一:参数路径(IService->Service->View)但参数如何传到viewmodel?
1.ContentPage(View层)添加 附加可绑定属性:天生没有但硬加的
/// <summary>
/// 导航参数的属性。
/// </summary>
public static readonly BindableProperty NavigationParameterProperty =
BindableProperty.CreateAttached("NavigationParameter",
typeof(object), typeof(NavigationContext), null,
BindingMode.OneWayToSource);
//是View层的属性
//定义附加可绑定属性:天生没有但硬加的
//属性名"NavigationParameter"
//值的类型是object
//定义在NavigationContext空间下
//默认值是null
//绑定的方向是OneWayToSource
2.导航到的viewmodel里应该有一个属性和NavigationParameterProperty配合工作,接收它。Poetry
/// <summary>
/// 诗词。
/// </summary>
public Poetry Poetry
{
get => _poetry;
set => Set(nameof(Poetry), ref _poetry, value);
}
/// <summary>
/// 诗词。
/// </summary>
private Poetry _poetry;
问题二:如何接收?数据绑定
1.在DetailPage(view)里引入属性所在的命名空间
xmlns:ls="clr-namespace:MasterDetailTemplate.Services;assembly=MasterDetailTemplate"
2.在ViewModelLocator里注册DetailPageViewModel
public class ViewModelLocator {
/// <summary>
/// 搜索结果页ViewModel。
/// </summary>
public ResultPageViewModel ResultPageViewModel =>
SimpleIoc.Default.GetInstance<ResultPageViewModel>();
/// <summary>
/// 诗词详情页ViewModel。
/// </summary>
public DetailPageViewModel DetailPageViewModel =>
SimpleIoc.Default.GetInstance<DetailPageViewModel>();
/// <summary>
/// ViewModelLocator
/// </summary>
public ViewModelLocator() {
SimpleIoc.Default.Register<ResultPageViewModel>();
SimpleIoc.Default.Register<DetailPageViewModel>();
SimpleIoc.Default.Register<IPoetryStorage, PoetryStorage>();
SimpleIoc.Default.Register<IPreferenceStorage, PreferenceStorage>();
}
}
3.设置BindingContext
BindingContext="{Binding DetailPageViewModel, Source={StaticResource ViewModelLocator}}"
4.属性绑定:将View的附加属性NavigationParameter的值绑定到ViewModel的Poetry属性
ls:NavigationContext.NavigationParameter="{Binding Poetry}"
问题三:如何设置NavigationParameter的值?
平台特性
/// <summary>
/// 设置导航参数。
/// </summary>
/// <param name="bindableObject">需要设置导航参数的对象。</param>
/// <param name="value">导航参数。</param>
public static void SetParameter(BindableObject bindableObject, object value) =>
bindableObject.SetValue(NavigationParameterProperty, value);
问题四:谁来设置参数?
谁导航谁设置 ContentNavigationService
/// <summary>
/// 导航到页面。
/// </summary>
/// <param name="pageKey">页面键。</param>
/// <param name="parameter">参数。</param>
public async Task NavigationToAsync(string pageKey, object parameter)
{
var page = _contentPageActivationService.Activate(pageKey); //取得目标页面
NavigationContext.SetParameter(page, parameter); //设参数
await MainPage.Detail.Navigation.PushAsync(page); //导航
}