public App ()
{
// The root page of your application
MainPage = new NavigationPage( new FirstContentPage() );
}
然后在你的第一个 ContentPage 调用中:
Navigation.PushAsync (new SecondContentPage ());
Android.Content.Res
进行导航。这似乎不正确,我应该从哪里导入它? - ChristianXamarin.Forms
内置支持多个导航宿主:
NavigationPage
,下一页从侧面滑入,TabbedPage
,这是你不喜欢的那个,CarouselPage
,可以左右切换到下一页/上一页。此外,所有页面还支持 PushModalAsync()
方法,该方法只是在现有页面之上推送新页面。
最后,如果您想确保用户无法返回到先前的页面(使用手势或后退硬件按钮),可以保持相同的 Page
显示并替换其 Content
。
替换根页面的建议选项同样有效,但您必须针对每个平台进行不同的处理。
如果您的项目已经设置为PCL表单项目(非常可能也是Shared Forms,但我没有尝试过),那么会有一个名为App.cs的类,其代码如下:
如果您的项目被设置为PCL表单项目(很可能也是共享表单,但我没有尝试过),那么会有一个名为App.cs的类,其代码如下:
public class App
{
public static Page GetMainPage ()
{
AuditorDB.Model.Extensions.AutoTimestamp = true;
return new NavigationPage (new LoginPage ());
}
}
您可以修改GetMainPage
方法,以返回在项目中定义的新的选项卡页面或其他页面。
从那里开始,您可以添加命令或事件处理程序来执行代码和操作。
// to show OtherPage and be able to go back
Navigation.PushAsync(new OtherPage());
// to show AnotherPage and not have a Back button
Navigation.PushModalAsync(new AnotherPage());
// to go back one step on the navigation stack
Navigation.PopAsync();
PushAsync()
无法正常工作,而PushModalAsync()
可以。 - knocte将新页面推送到堆栈中,然后移除当前页面。这将导致切换。
item.Tapped += async (sender, e) => {
await Navigation.PushAsync (new SecondPage ());
Navigation.RemovePage(this);
};
您需要先进入导航页面:
MainPage = NavigationPage(new FirstPage());
切换内容并不理想,因为您只有一个大页面和一个页面事件集,例如 OnAppearing 等。
await Navigation.PushAsync(new SecondPage(), false);
- Damian Green App.Current.MainPage = new HomePage();
Navigation.PushModalAsync(new HomePage())
Page
(视图)不应该知道任何关于ViewModel
的信息,反之亦然。下面是一个很好的违规示例:// C# version
public partial class MyPage : ContentPage
{
public MyPage()
{
InitializeComponent();
// Violation
this.BindingContext = new MyViewModel();
}
}
// XAML version
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
x:Class="MyApp.Views.MyPage">
<ContentPage.BindingContext>
<!-- Violation -->
<viewmodels:MyViewModel />
</ContentPage.BindingContext>
</ContentPage>
ViewModels
之间导航,而不是在Pages
(Views)之间导航。除了明确分离关注点之外,其中一个优点是您可以轻松地将参数传递给下一个ViewModel
或在导航后立即执行异步初始化代码。现在进入细节。App
中保留对它的引用,以便全局可用(这不是一个好主意,但为了简化需要):public class DependencyResolver
{
static IContainer container;
public DependencyResolver(params Module[] modules)
{
var builder = new ContainerBuilder();
if (modules != null)
foreach (var module in modules)
builder.RegisterModule(module);
container = builder.Build();
}
public T Resolve<T>() => container.Resolve<T>();
public object Resolve(Type type) => container.Resolve(type);
}
public partial class App : Application
{
public DependencyResolver DependencyResolver { get; }
// Pass here platform specific dependencies
public App(Module platformIocModule)
{
InitializeComponent();
DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
MainPage = new WelcomeView();
}
/* The rest of the code ... */
}
2. 我们需要一个对象负责检索特定ViewModel
的Page
(视图),反之亦然。在设置应用程序的根/主页面时,第二种情况可能很有用。为此,我们应该约定所有ViewModels
都应该在ViewModels
目录中,而Pages
(Views)应该在Views
目录中。换句话说,ViewModels
应该位于[MyApp].ViewModels
命名空间中,而Pages
(Views)应该位于[MyApp].Views
命名空间中。此外,我们应该约定WelcomeView
(页面)应该有一个WelcomeViewModel
等。以下是一个映射器的代码示例:
public class TypeMapperService
{
public Type MapViewModelToView(Type viewModelType)
{
var viewName = viewModelType.FullName.Replace("Model", string.Empty);
var viewAssemblyName = GetTypeAssemblyName(viewModelType);
var viewTypeName = GenerateTypeName("{0}, {1}", viewName, viewAssemblyName);
return Type.GetType(viewTypeName);
}
public Type MapViewToViewModel(Type viewType)
{
var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewModelAssemblyName = GetTypeAssemblyName(viewType);
var viewTypeModelName = GenerateTypeName("{0}Model, {1}", viewModelName, viewModelAssemblyName);
return Type.GetType(viewTypeModelName);
}
string GetTypeAssemblyName(Type type) => type.GetTypeInfo().Assembly.FullName;
string GenerateTypeName(string format, string typeName, string assemblyName) =>
string.Format(CultureInfo.InvariantCulture, format, typeName, assemblyName);
}
3. 对于设置根页面的情况,我们需要一种类似于ViewModelLocator
的东西来自动设置BindingContext
:
public static class ViewModelLocator
{
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable) =>
(bool)bindable.GetValue(AutoWireViewModelProperty);
public static void SetAutoWireViewModel(BindableObject bindable, bool value) =>
bindable.SetValue(AutoWireViewModelProperty, value);
static ITypeMapperService mapper = (Application.Current as App).DependencyResolver.Resolve<ITypeMapperService>();
static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
var viewType = view.GetType();
var viewModelType = mapper.MapViewToViewModel(viewType);
var viewModel = (Application.Current as App).DependencyResolver.Resolve(viewModelType);
view.BindingContext = viewModel;
}
}
// Usage example
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
viewmodels:ViewModelLocator.AutoWireViewModel="true"
x:Class="MyApp.Views.MyPage">
</ContentPage>
4.最后,我们需要一个NavigationService
来支持ViewModel First Navigation
方法:
public class NavigationService
{
TypeMapperService mapperService { get; }
public NavigationService(TypeMapperService mapperService)
{
this.mapperService = mapperService;
}
protected Page CreatePage(Type viewModelType)
{
Type pageType = mapperService.MapViewModelToView(viewModelType);
if (pageType == null)
{
throw new Exception($"Cannot locate page type for {viewModelType}");
}
return Activator.CreateInstance(pageType) as Page;
}
protected Page GetCurrentPage()
{
var mainPage = Application.Current.MainPage;
if (mainPage is MasterDetailPage)
{
return ((MasterDetailPage)mainPage).Detail;
}
// TabbedPage : MultiPage<Page>
// CarouselPage : MultiPage<ContentPage>
if (mainPage is TabbedPage || mainPage is CarouselPage)
{
return ((MultiPage<Page>)mainPage).CurrentPage;
}
return mainPage;
}
public Task PushAsync(Page page, bool animated = true)
{
var navigationPage = Application.Current.MainPage as NavigationPage;
return navigationPage.PushAsync(page, animated);
}
public Task PopAsync(bool animated = true)
{
var mainPage = Application.Current.MainPage as NavigationPage;
return mainPage.Navigation.PopAsync(animated);
}
public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel =>
InternalPushModalAsync(typeof(TViewModel), animated, parameter);
public Task PopModalAsync(bool animated = true)
{
var mainPage = GetCurrentPage();
if (mainPage != null)
return mainPage.Navigation.PopModalAsync(animated);
throw new Exception("Current page is null.");
}
async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter)
{
var page = CreatePage(viewModelType);
var currentNavigationPage = GetCurrentPage();
if (currentNavigationPage != null)
{
await currentNavigationPage.Navigation.PushModalAsync(page, animated);
}
else
{
throw new Exception("Current page is null.");
}
await (page.BindingContext as BaseViewModel).InitializeAsync(parameter);
}
}
正如您所看到的,这里有一个BaseViewModel
——所有ViewModels
的抽象基类,您可以在其中定义像InitializeAsync
这样的方法,在导航后立即执行。下面是一个导航示例:
public class WelcomeViewModel : BaseViewModel
{
public ICommand NewGameCmd { get; }
public ICommand TopScoreCmd { get; }
public ICommand AboutCmd { get; }
public WelcomeViewModel(INavigationService navigation) : base(navigation)
{
NewGameCmd = new Command(async () => await Navigation.PushModalAsync<GameViewModel>());
TopScoreCmd = new Command(async () => await navigation.PushModalAsync<TopScoreViewModel>());
AboutCmd = new Command(async () => await navigation.PushModalAsync<AboutViewModel>());
}
}
ViewModel First Navigation
方法有很多好的文章,还有一本免费的使用Xamarin.Forms的企业应用程序模式电子书,详细介绍了这个和其他许多有趣的主题。In App.Xaml.Cs:
MainPage = new NavigationPage( new YourPage());
await Navigation.PushAsync(new YourSecondPage());
您可以在此处了解更多关于Xamarin Forms导航的信息: https://learn.microsoft.com/zh-cn/xamarin/xamarin-forms/app-fundamentals/navigation/hierarchical
Microsoft文档非常好。
还有一个更新的概念是Shell
。它允许以一种新的方式构建应用程序并在某些情况下简化导航。
介绍:https://devblogs.microsoft.com/xamarin/shell-xamarin-forms-4-0-getting-started/
基本Shell视频教程:https://www.youtube.com/watch?v=0y1bUAcOjZY&t=3112s
文档:https://learn.microsoft.com/zh-cn/xamarin/xamarin-forms/app-fundamentals/shell/
http://developer.xamarin.com/guides/cross-platform/xamarin-forms/introduction-to-xamarin-forms/
~~~
public class MainActivity : AndroidActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Xamarin.Forms.Forms.Init(this, bundle);
// Set our view from the "main" layout resource
SetPage(BuildView());
}
static Page BuildView()
{
var mainNav = new NavigationPage(new RootPage());
return mainNav;
}
}
public class RootPage : ContentPage
{
async void ShowLoginDialog()
{
var page = new LoginPage();
await Navigation.PushModalAsync(page);
}
}
//为简化代码已删除,仅显示弹出窗口
private async void AuthenticationResult(bool isValid)
{
await navigation.PopModalAsync();
}
呼叫:
((App)App.Current).ChangeScreen(new Map());
public void ChangeScreen(Page page)
{
MainPage = page;
}
NavigationPage
的页面,它包含一堆 ContentPages
。
NavigationPage
有像 PushAsync()
和 PopAsync()
这样的方法。PushAsync 在堆栈顶部添加页面,此时该页面将成为当前活动页面。 PopAsync() 方法从堆栈顶部删除页面。App.Xaml.Cs
中进行设置。MainPage = new NavigationPage( new YourPage());
YourPage
页面开始,您可以使用await Navigation.PushAsync(new newPage());
方法将newPage添加到堆栈的顶部。此时newPage将成为当前活动页面。