【问题标题】:Xamarin Forms Navigation without animation没有动画的 Xamarin 表单导航
【发布时间】:2014-11-14 15:45:50
【问题描述】:

我有一个应用程序,我想在其中显示页面 A,用户可以从中导航到页面 B 或 C,从 B 返回到 A 或 C,并且从 C 仅返回到 A,即使用户通过B 到达 C

目前,当我执行 B->C 转换时,我首先 PopAsync 回到 A,然后我执行 PushAsync 回到 C,这样 '

问题是:有没有一种文明的方式来设置这个导航方案,同时仍然依赖内置的 Navigation 来跟踪导航堆栈 - 我不想自己这样做并使用 PushModalAsync

注意(如图所示)A和C不是整个导航栈的终点,A之前和C之后都有页面,所以必须保留栈。

【问题讨论】:

    标签: xamarin xamarin.forms


    【解决方案1】:

    目前 Xamarin Forms 导航非常简陋,我怀疑是否有一种很好的方法可以实现这一点。除了 Doing 和必要时额外的“Pop”。

    【讨论】:

      【解决方案2】:

      在 iOS 上,NavigationRenderer 具有虚拟方法 OnPopViewAsyncOnPushAsync(类似 on 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. 添加“动画”属性。
      3. 为您的自定义导航页面派生自定义导航渲染器。
      4. 使用“Animated”属性覆盖调用其基方法的 pop 和 push 方法。

      请注意,我还没有尝试过这种方法,因为它需要做很多工作。但是禁用所有导航页面上的动画确实可以这样工作。


      编辑:我花了几个小时才真正为自己的项目实施解决方案。因此,我将分享更多细节。 (我在 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 属性传递给它们的基本方法。 (这种方式注入flag有点丑,但我找不到更好的解决方案。)

      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 类只是创建了一个新的DemoPage 包装到CustomNavigationPage 中。请注意,此示例必须可公开访问此实例。

      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!否则立即调用两个或多个 pop 或 push 会导致奇怪的行为和崩溃。

      【讨论】:

      • 我无法添加 Thread.Sleep(10)。它找不到它。有什么问题?
      • @AEMLoviji:我是using System.Threading 获取方法Thread.Sleep
      • 我知道 Thread.Sleep 包含在 System.Threadin 中。但在 Xamarin 项目中我看不到它......
      • 也许你应该使用 async () => await Task.Delay(10) 而不是 Thread.Sleep(10) 因为 Thread.Sleep (据我所知)不保证等待正确的线程.
      【解决方案3】:

      这是我整理的一系列 sn-ps 以及其他一些细节,用于改进 iOS 的 NaviagationPage。 Link to comment and code on xamarin forums.

      【讨论】:

        【解决方案4】:

        如果我这样做,我会做的是将页面 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)。小心!
        【解决方案5】:

        @法尔科 您现在可以包含一个布尔参数:

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

        Xamarin Documentation

        【讨论】:

        • 是否还有一个全局设置也会影响例如后按导航?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-24
        • 1970-01-01
        • 2018-02-07
        • 2021-12-08
        • 2018-11-30
        相关资源
        最近更新 更多