Xamarin Forms无动画导航

4
我有一个应用程序,想展示页面A,用户可以从该页面导航到页面B或C,从B返回A或C,从C仅能返回A,即使用户通过B进入C。
目前当我执行B到C的转换时,我首先使用PopAsync返回到A,然后使用PushAsync进入C,以便“保留”导航堆栈。
问题是:是否有一种文明的方式来设置这种导航方案,同时仍然依赖内置的导航来跟踪导航堆栈 - 我不想自己做这个并使用PushModalAsync
请注意(如图所示),A和C不是整个导航堆栈的终点,A之前和C之后还有其他页面,因此必须保留堆栈。
5个回答

6
在iOS上,NavigationRenderer有虚拟方法OnPopViewAsyncOnPushAsync(类似于Android):
protected override Task<bool> OnPopViewAsync(Page page, bool animated)
{
    return base.OnPopViewAsync(page, animated);
}

protected override Task<bool> OnPushAsync(Page page, bool animated)
{
    return base.OnPushAsync(page, animated);
}

他们使用两个参数,页面和是否动画转换,调用相应的基本方法。因此,您可以使用以下方法启用或禁用动画:
1. 派生自定义导航页。 2. 添加“Animated”属性。 3. 为自定义导航页派生自定义导航渲染器。 4. 覆盖弹出和推送方法,并使用“Animated”属性调用它们的基本方法。
请注意,我尚未尝试过此方法,因为需要付出很多工作。但是,使用此方法禁用所有导航页面上的动画确实有效。
编辑:我花了几个小时来实际实现我的解决方案,因此我将分享更多细节。(我在Xamarin.Forms 1.2.3-pre4上开发和测试。)
自定义导航页
除了上述“Animated”属性外,我的导航页重新实现了两个转换函数并添加了一个可选参数“animated”,默认情况下为true。这样,我们将能够保留所有现有代码,并仅在需要时添加false。
此外,两种方法将在推送/弹出页面后短暂休眠(10毫秒)。如果没有此延迟,我们将遇到连续调用的问题。
public class CustomNavigationPage: NavigationPage
{
    public bool Animated { get; private set; }

    public CustomNavigationPage(Page page) : base(page)
    {
    }

    // Analysis disable once MethodOverloadWithOptionalParameter
    public async Task PushAsync(Page page, bool animated = true)
    {
        Animated = animated;
        await base.PushAsync(page);
        await Task.Run(delegate {
            Thread.Sleep(10);
        });
    }

    // Analysis disable once MethodOverloadWithOptionalParameter
    public async Task<Page> PopAsync(bool animated = true)
    {
        Animated = animated;
        var task = await base.PopAsync();
        await Task.Run(delegate {
            Thread.Sleep(10);
        });
        return task;
    }
}

自定义导航渲染器

我的自定义导航页面的渲染器覆盖了两个过渡方法,并将Animated属性传递给它们的基本方法。(这种方式注入标志有点丑陋,但我找不到更好的解决方案。)

public class CustomNavigationRenderer: NavigationRenderer
{
    protected override Task<bool> OnPopViewAsync(Page page, bool animated)
    {
        return base.OnPopViewAsync(page, (Element as CustomNavigationPage).Animated);
    }

    protected override Task<bool> OnPushAsync(Page page, bool animated)
    {
        return base.OnPushAsync(page, (Element as CustomNavigationPage).Animated);
    }
}

这是针对iOS的。但在Android上几乎完全相同。

一个示例应用程序

为了演示连续推送和弹出页面的可能性,我编写了以下应用程序。

App类只是创建了一个包装在CustomNavigationPage中的新的DemoPage。请注意,此实例必须公开访问以供本示例使用。

public static class App
{
    public static CustomNavigationPage NavigationPage;

    public static Page GetMainPage()
    {    
        return NavigationPage = new CustomNavigationPage(new DemoPage("Root"));
    }
}

演示页面包含许多按钮,可以按不同顺序推入和弹出页面。您可以为每次调用PushAsyncPopAsync添加或删除false选项。

public class DemoPage: ContentPage
{
    public DemoPage(string title)
    {
        Title = title;
        Content = new StackLayout {
            Children = {
                new Button {
                    Text = "Push",
                    Command = new Command(o => App.NavigationPage.PushAsync(new DemoPage("Pushed"))),
                },
                new Button {
                    Text = "Pop",
                    Command = new Command(o => App.NavigationPage.PopAsync()),
                },
                new Button {
                    Text = "Push + Pop",
                    Command = new Command(async o => {
                        await App.NavigationPage.PushAsync(new DemoPage("Pushed (will pop immediately)"));
                        await App.NavigationPage.PopAsync();
                    }),
                },
                new Button {
                    Text = "Pop + Push",
                    Command = new Command(async o => {
                        await App.NavigationPage.PopAsync(false);
                        await App.NavigationPage.PushAsync(new DemoPage("Popped and pushed immediately"));
                    }),
                },
                new Button {
                    Text = "Push twice",
                    Command = new Command(async o => {
                        await App.NavigationPage.PushAsync(new DemoPage("Pushed (1/2)"), false);
                        await App.NavigationPage.PushAsync(new DemoPage("Pushed (2/2)"));
                    }),
                },
                new Button {
                    Text = "Pop twice",
                    Command = new Command(async o => {
                        await App.NavigationPage.PopAsync(false);
                        await App.NavigationPage.PopAsync();
                    }),
                },
            },
        };
    }
}

重要提示:我花费了数小时的调试时间才发现您需要使用NavigationPage(或其派生类),而不是ContentPageNavigation!否则,立即调用两个或更多的弹出或推送将导致奇怪的行为和崩溃。


我无法添加Thread.Sleep(10),它找不到它。问题出在哪里? - AEMLoviji
@AEMLoviji:我正在使用System.Threading来获取Thread.Sleep方法。 - Falko
1
我知道Thread.Sleep包含在System.Threading中。但是在Xamarin项目中,我看不到它... - AEMLoviji
也许你应该使用 async () => await Task.Delay(10) 而不是 Thread.Sleep(10),因为(据我所知)Thread.Sleep 不能保证在正确的线程上等待。 - thomasgalliker

5

现在您有可能包含一个布尔参数:

 Navigation.PushAsync (new Page2Xaml (), false);

Xamarin 文档

(这是一个关于页面转换动画的链接)

还有一个全局设置,是否也会影响例如后退按钮的导航? - lucidbrot

0

0

目前Xamarin Forms的导航功能非常简陋,我怀疑是否有一种好的方法来实现它。除了在必要时进行额外的“弹出”操作。


0
如果我在做这件事情,我会将页面C推入您的NavigationStack,然后将页面B从堆栈中移除。这样,当您从页面C弹出时,您将转到页面A。
// Push the page you want to go to on top of the stack.    
await NavigationPage.PushAsync(new CPage()));

// Remove page B from the stack, so when you want to go back next time 
//you will go to page A.   
Navigation.RemovePage(Navigation.NavigationStack[Navigation.NavigationStack.Count - 2] );

或者说,当你从页面 C 弹出时,您可以从堆栈中移除类型为页面 B 的所有实例,然后向后弹出 1。在这种情况下,页面 B 将一直留在堆栈中,直到您要从页面 C 返回页面 A。


RemovePage似乎是一个公共方法,但是我过去曾经遇到过奇怪的异常(在Xamarin iOS上的某些渲染器中出现了NullReferenceException)。要小心! - thomasgalliker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接