如何在Xamarin Forms中切换页面?

Eri*_*ric 90 xamarin xamarin.forms

如何在Xamarin Forms中切换页面?我的主页是一个ContentPage,我不想切换到标签页.

我已经能够通过找到应该触发新页面的控件的父级来伪造它,直到我找到ContentPage,然后将内容与控件换成新页面.但这看起来真的很草率.

谢谢

Ste*_*oix 61

Xamarin.Forms 支持内置多个导航主机:

  • NavigationPage,下一页插入的位置,
  • TabbedPage,你不喜欢的那个
  • CarouselPage,允许左右切换到下一页/上一页.

除此之外,所有页面还支持PushModalAsync()在现有页面之上推送新页面.

最后,如果您想确保用户无法返回上一页(使用手势或后部硬件按钮),您可以保持Page显示并替换它Content.

替换根页的建议选项也适用,但您必须为每个平台处理不同的方法.


Dav*_*las 57

在App类中,您可以将MainPage设置为导航页面并将根页面设置为您的ContentPage:

public App ()
{
    // The root page of your application
    MainPage = new NavigationPage( new FirstContentPage() );
}
Run Code Online (Sandbox Code Playgroud)

然后在您的第一个ContentPage调用中:

Navigation.PushAsync (new SecondContentPage ());
Run Code Online (Sandbox Code Playgroud)


Ste*_*rov 39

如果您的项目已经设置为PCL表单项目(很可能也作为共享表单,但我还没有尝试过),那么有一个类App.cs如下所示:

public class App
{
    public static Page GetMainPage ()
    {     
        AuditorDB.Model.Extensions.AutoTimestamp = true;
        return new NavigationPage (new LoginPage ());
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以修改GetMainPage方法以返回新的TabbedPaged或您在项目中定义的其他页面

从那以后,您可以添加命令或事件处理程序来执行代码

// 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();
Run Code Online (Sandbox Code Playgroud)

  • 这不会在页面之间切换.这只会更改最初加载的页面. (2认同)

Dan*_*rts 23

将新页面推入堆栈,然后删除当前页面.这导致切换.

item.Tapped += async (sender, e) => {
    await Navigation.PushAsync (new SecondPage ());
    Navigation.RemovePage(this);
};
Run Code Online (Sandbox Code Playgroud)

您需要先进入导航页面:

MainPage = NavigationPage(new FirstPage());
Run Code Online (Sandbox Code Playgroud)

切换内容并不理想,因为您只有一个大页面和一组页面事件,如OnAppearing等.

  • 方便提示 - 在更改页面时删除转换,添加false作为第二个参数:`await Navigation.PushAsync(new SecondPage(),false);` (2认同)

Baq*_*qvi 7

如果您不想转到上一页,即授权完成后不让用户返回登录屏幕,则可以使用。

 App.Current.MainPage = new HomePage();
Run Code Online (Sandbox Code Playgroud)

如果要启用后退功能,只需使用

Navigation.PushModalAsync(new HomePage())
Run Code Online (Sandbox Code Playgroud)


EvZ*_*EvZ 6

似乎这个线程很受欢迎,如果不在这里提及有另一种方法 - ViewModel First Navigation. 大多数 MVVM 框架都在使用它,但是如果您想了解它的含义,请继续阅读。

所有官方 Xamarin.Forms 文档都展示了一个简单但略微不是 MVVM 纯解决方案。那是因为Page(View) 应该对 一无所知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>
Run Code Online (Sandbox Code Playgroud)

如果您有一个 2 页的应用程序,这种方法可能对您有好处。但是,如果您正在开发大型企业解决方案,则最好采用一种ViewModel First Navigation方法。这是一种稍微复杂但更清晰的方法,它允许您ViewModelsPages(视图)之间导航而不是在(视图)之间导航。除了明确的关注点分离之外的优势之一是您可以轻松地将参数传递给下一个ViewModel或在导航后立即执行异步初始化代码。现在到细节。

(我会尽量简化所有的代码示例)。

1. 首先,我们需要一个地方,我们可以在其中注册我们所有的对象并可以选择定义它们的生命周期。这件事我们可以使用一个IOC容器,你可以自己选择一个。在这个例子中,我将使用Autofac(它是最快的可用之一)。我们可以保留对它的引用,以便在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 ... */
}
Run Code Online (Sandbox Code Playgroud)

2.我们将需要一个负责为Page特定检索(视图)的对象ViewModel,反之亦然。第二种情况在设置应用程序的根/主页的情况下可能很有用。为此,我们应该同意一个简单的约定,即所有的ViewModels都应该在ViewModels目录中,并且Pages(视图)应该在Views目录中。换句话说,ViewModels应该存在于[MyApp].ViewModels命名空间中,而Pages(Views)应该存在于命名[MyApp].Views空间中。除此之外,我们应该同意WelcomeView(Page) 应该有一个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);
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,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>());
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所了解的那样,这种方法更复杂、更难调试并且可能会令人困惑。然而,它有很多优点,而且您实际上不必自己实现它,因为大多数 MVVM 框架都开箱即用地支持它。此处演示的代码示例可在github 上找到

有很多关于ViewModel First Navigation方法的好文章,还有一个免费的Enterprise Application Patterns using Xamarin.Forms电子书,它详细解释了这一点和许多其他有趣的主题。