【问题标题】:How to handle/cancel back navigation in Xamarin Forms如何在 Xamarin Forms 中处理/取消导航
【发布时间】:2015-08-19 20:41:02
【问题描述】:

我试图通过覆盖OnBackButtonPressed 来使用后退导航,但不知何故它根本没有被调用。我正在使用ContentPage 和最新的 1.4.2 版本。

【问题讨论】:

  • 我相信该事件是专门针对 Android 硬件后退按钮的,而不是一般的“后退”导航事件。
  • 我知道。我尝试了 Android 和硬件后退按钮,没有被调用

标签: xamarin navigation xamarin.forms


【解决方案1】:

您是对的,如果您想阻止导航,请在您的页面类中覆盖 OnBackButtonPressed 并返回 true。它对我来说很好,我有相同的版本。

protected override bool OnBackButtonPressed()
{
    if (Condition)
        return true;
    return base.OnBackButtonPressed();
}

【讨论】:

  • Xamarin Forms 中似乎存在一个错误,即如果我使用 TabbedPage 作为根页面,则不会调用 OnBackButtonPressed。一个貌似来自 Xamarin 的人告诉我
  • 如何获取正在导航到的页面?
  • The docs 说“此事件未在 iOS 上引发。”
【解决方案2】:

根据您正在寻找的具体内容(如果您只是想取消后退按钮导航,我不建议您使用此功能),OnDisappearing 可能是另一种选择:

protected override void OnDisappearing()
{
       //back button logic here
}

【讨论】:

  • 当我尝试重写 OnDisappear 时有两个问题: 1. 页面已经在视觉上消失了,所以“你确定吗?”对话框出现在前一页上。 2. 无法取消返回动作。
  • here 是特定于平台的解决方案
  • OnDisappearing 功能在您锁定手机屏幕或切换到另一个应用程序时也会调用。不仅在您导航到上一页时
【解决方案3】:
 protected override bool OnBackButtonPressed()
        {
            base.OnBackButtonPressed();
                return true;
        }

base.OnBackButtonPressed() 在单击硬件后退按钮时返回 false。 为了防止后退按钮的操作或防止导航到上一页。覆盖函数应返回为真。返回 true 时,它​​停留在当前的 xamarin 表单页面上,并且页面的状态也保持不变。

【讨论】:

    【解决方案4】:

    OnBackButtonPressed() 这将在按下硬件后退按钮时调用,就像在 android 中一样。 这不适用于在 ios 中按下软件后退按钮。

    【讨论】:

      【解决方案5】:

      对于仍在与此问题作斗争的任何人 - 基本上您无法拦截跨平台的返回导航。话虽如此,有两种方法可以有效解决问题:

      1. 使用 NavigationPage.ShowHasBackButton(this, false) 隐藏 NavigationPage 后退按钮并推送具有自定义后退/取消/关闭按钮的模式页面

      2. 为每个平台本机拦截后退导航。这是一篇适用于 iOS 和 Android 的好文章:https://theconfuzedsourcecode.wordpress.com/2017/03/12/lets-override-navigation-bar-back-button-click-in-xamarin-forms/

      对于 UWP,你只能靠自己了 :)

      编辑:

      好吧,自从我这样做了 :) 它实际上非常简单 - 只有一个后退按钮,并且它受 Forms 支持,因此您只需覆盖 ContentPage 的 OnBackButtonPressed:

          protected override bool OnBackButtonPressed()
          {
              if (Device.RuntimePlatform.Equals(Device.UWP))
              {
                  OnClosePageRequested();
                  return true;
              }
              else
              {
                  base.OnBackButtonPressed();
                  return false;
              }
          }
      
          async void OnClosePageRequested()
          {
              var tdvm = (TaskDetailsViewModel)BindingContext;
              if (tdvm.CanSaveTask())
              {
                  var result = await DisplayAlert("Wait", "You have unsaved changes! Are you sure you want to go back?", "Discard changes", "Cancel");
      
                  if (result)
                  {
                      tdvm.DiscardChanges();
                      await Navigation.PopAsync(true);
                  }
              }
              else
              {
                  await Navigation.PopAsync(true);
              }           
          }
      

      【讨论】:

        【解决方案6】:

        好吧,经过几个小时我想出了这个。它分为三个部分。

        #1 处理 android 上的硬件后退按钮。这个很简单,重写 OnBackButtonPressed。请记住,这仅适用于硬件后退按钮和 android。它不会处理导航栏后退按钮。如您所见,我在退出页面之前尝试通过浏览器进行返回,但您可以将所需的任何逻辑放入其中。

          protected override bool OnBackButtonPressed()
            {
                if (_browser.CanGoBack)
                {
                    _browser.GoBack();
                    return true;
                }
                else
                {
                    //await Navigation.PopAsync(true);
                    base.OnBackButtonPressed();
                    return true;
                }
            }
        

        #2 iOS 导航返回按钮。这个真的很棘手,如果你环顾网络,你会发现一些用新的自定义按钮替换后退按钮的例子,但几乎不可能让它看起来像你的其他页面。在这种情况下,我制作了一个位于普通按钮顶部的透明按钮。

        [assembly: ExportRenderer(typeof(MyAdvantagePage), typeof
        
        (MyAdvantagePageRenderer))]
        namespace Advantage.MyAdvantage.MobileApp.iOS.Renderers
        {
            public class MyAdvantagePageRenderer : Xamarin.Forms.Platform.iOS.PageRenderer
            {
                public override void ViewWillAppear(bool animated)
                {
                    base.ViewWillAppear(animated);
        
                    if (((MyAdvantagePage)Element).EnableBackButtonOverride)
                    {
                        SetCustomBackButton();
                    }
                }
                private void SetCustomBackButton()
                {
                    UIButton btn = new UIButton();
                    btn.Frame = new CGRect(0, 0, 50, 40);
                    btn.BackgroundColor = UIColor.Clear;
        
                    btn.TouchDown += (sender, e) =>
                    {
                        // Whatever your custom back button click handling
                        if (((MyAdvantagePage)Element)?.
                        CustomBackButtonAction != null)
                        {
                            ((MyAdvantagePage)Element)?.
                               CustomBackButtonAction.Invoke();
                        }
                    };
                    NavigationController.NavigationBar.AddSubview(btn);
                }
            }
        }
        

        Android,很棘手。在旧版本和未来版本的表单中,一旦修复,您可以像这样简单地覆盖 OnOptionsItemselected

               public override bool OnOptionsItemSelected(IMenuItem item)
            {
                // check if the current item id 
                // is equals to the back button id
                if (item.ItemId == 16908332)
                {
                    // retrieve the current xamarin forms page instance
                    var currentpage = (MyAdvantagePage)
                    Xamarin.Forms.Application.
                    Current.MainPage.Navigation.
                    NavigationStack.LastOrDefault();
        
                    // check if the page has subscribed to 
                    // the custom back button event
                    if (currentpage?.CustomBackButtonAction != null)
                    {
                        // invoke the Custom back button action
                        currentpage?.CustomBackButtonAction.Invoke();
                        // and disable the default back button action
                        return false;
                    }
        
                    // if its not subscribed then go ahead 
                    // with the default back button action
                    return base.OnOptionsItemSelected(item);
                }
                else
                {
                    // since its not the back button 
                    //click, pass the event to the base
                    return base.OnOptionsItemSelected(item);
                }
            }
        

        但是,如果您使用的是 FormsAppCompatActivity,那么您需要在 MainActivity 的 OnCreate 中添加此项来设置您的工具栏:

        Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
                    SetSupportActionBar(toolbar);
        

        但是等等!如果您的 .Forms 版本太旧或版本太新,则会出现工具栏为空的错误。如果发生这种情况,我让它工作以制定最后期限的破解方式是这样的。在OnCreateMainActivity

                MobileApp.Pages.Articles.ArticleDetail.androdAction = () =>
                {
                    Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
                    SetSupportActionBar(toolbar);
                };
        

        ArticleDetail 是一个页面,而 androidAction 是一个 Action,如果我的页面上的平台是 Android,我会在 OnAppearing 上运行它。至此,您的应用中的工具栏将不再为空。

        再加上几个步骤,我们在上面制作的 iOS 渲染使用了您需要添加到您正在为其制作渲染器的任何页面的属性。我正在为我制作的 MyAdvantagePage 类制作它,它实现了 ContentPage 。所以在我的MyAdvantagePage 课程中我添加了

        public Action CustomBackButtonAction { get; set; }
        
                public static readonly BindableProperty EnableBackButtonOverrideProperty =
                       BindableProperty.Create(
                       nameof(EnableBackButtonOverride),
                       typeof(bool),
                       typeof(MyAdvantagePage),
                       false);
        
                /// <summary>
                /// Gets or Sets Custom Back button overriding state
                /// </summary>
                public bool EnableBackButtonOverride
                {
                    get
                    {
                        return (bool)GetValue(EnableBackButtonOverrideProperty);
                    }
                    set
                    {
                        SetValue(EnableBackButtonOverrideProperty, value);
                    }
                }
        

        现在一切都完成了,我可以在我的任何MyAdvantagePage 上添加这个

        :
        
        
         this.EnableBackButtonOverride = true;
                    this.CustomBackButtonAction = async () =>
                    {
                        if (_browser.CanGoBack)
                        {
                            _browser.GoBack();
                        }
                        else
                        {
                            await Navigation.PopAsync(true);
                        }
                    };
        

        这应该是让它在 Android 硬件上工作的一切,并为 android 和 iOS 导航回来。

        【讨论】:

        • 所以...如果你有一个 TabbedPage 并使用我可怕的 hack 来解决 Xamarin.Forms 错误,它只会捕获第一页上后退按钮上的后退按钮覆盖,显然其他页面上的后退按钮不同......?有没有人修复所有标签?
        • 这条线是什么? MobileApp.Pages.Articles.ArticleDetail.androdAction。我不知道如何解决这一切。我需要包括什么用途?
        • @Andrew 我试图在它下面解释它。 ArticleDetail 是 PageandrodAction 是我添加到 Page 类的 Action。我在我的页面上覆盖 OnAppearing 并在该事件期间运行该操作。
        【解决方案7】:

        诀窍是实现您自己的导航页面,该页面继承自NavigationPage。它有相应的事件PushedPoppedPoppedToRoot

        示例实现可能如下所示:

        public class PageLifetimeSupportingNavigationPage : NavigationPage
        {
            public PageLifetimeSupportingNavigationPage(Page content)
                : base(content)
            {
                Init();
            }
        
            private void Init()
            {
                Pushed += (sender, e) => OpenPage(e.Page);
        
                Popped += (sender, e) => ClosePage(e.Page);
        
                PoppedToRoot += (sender, e) =>
                {
                    var args = e as PoppedToRootEventArgs;
                    if (args == null)
                        return;
        
                    foreach (var page in args.PoppedPages.Reverse())
                        ClosePage(page);
                };
            }
        
            private static void OpenPage(Page page)
            {
                if (page is IPageLifetime navpage)
                    navpage.OnOpening();
            }
        
            private static void ClosePage(Page page)
            {
                if (page is IPageLifetime navpage)
                    navpage.OnClosed();
        
                page.BindingContext = null;
            }
        }
        

        页面将实现以下接口:

        public interface IPageLifetime
        {
            void OnOpening();
            void OnClosed();
        }
        

        这个接口可以在所有页面的基类中实现,然后将它的调用委托给它的视图模型。

        导航页面可以这样创建:

        var navigationPage = new PageLifetimeSupportingNavigationPage(new MainPage());
        

        MainPage 将是要显示的根页面。

        当然,您也可以首先使用NavigationPage 并订阅它的事件而不从它继承。

        【讨论】:

          【解决方案8】:

          也许这会有用,你需要隐藏后退按钮,然后用你自己的按钮替换:

          public static UIViewController AddBackButton(this UIViewController controller, EventHandler ev){
              controller.NavigationItem.HidesBackButton = true;
              var btn = new UIBarButtonItem(UIImage.FromFile("myIcon.png"), UIBarButtonItemStyle.Plain, ev);
              UIBarButtonItem[] items = new[] { btn };
              controller.NavigationItem.LeftBarButtonItems = items;
              return controller;
          }
          
          public static UIViewController DeleteBack(this UIViewController controller)
          {
              controller.NavigationItem.LeftBarButtonItems = null;
              return controller;
          }
          

          然后将它们调用到这些方法中:

          public override void ViewWillAppear(bool animated)
          {
              base.ViewWillAppear(animated);
              this.AddBackButton(DoSomething);
              UpdateFrames();
          }
          
          public override void ViewWillDisappear(Boolean animated)
          {
              this.DeleteBackButton();
          }
          
          public void DoSomething(object sender, EventArgs e)
          {            
              //Do a barrel roll
          }
          

          【讨论】:

            【解决方案9】:

            另一种方法是使用Rg.Plugins.Popup,它允许您实现漂亮的弹出窗口。它使用另一个 NavigationStack => Rg.Plugins.Popup.Services.PopupNavigation.Instance.PopupStack。所以你的页面不会被 NavigationBar 环绕。

            在你的情况下,我会简单

            • 创建一个不透明背景的整页弹出窗口
            • 在 ⚠️ParentPage⚠️ 上覆盖 ↩️ OnBackButtonPressed for Android:

              protected override bool OnBackButtonPressed() { return Rg.Plugins.Popup.Services.PopupNavigation.Instance.PopupStack.Any(); }

            由于后退按钮会影响通常的 NavigationStack,因此当您的“弹出窗口正在显示”时,只要用户尝试使用它,您的父级就会弹出。

            现在呢? Xaml 你想用你想要的所有检查正确关闭你的弹出窗口。

            ? 这些目标的问题解决了?

            • [x] 安卓
            • [x] iOS
            • [-] Windows Phone(已过时。如果需要 WP,请使用 v1.1.0-pre5)
            • [x] UWP(最小目标:10.0.16299)

            【讨论】:

              【解决方案10】:

              Kyle 答案的补充 设置

              在您的页面内

              public static Action SetToolbar;
              

              您的页面出现

              if (Device.RuntimePlatform == Device.Android)
              {
                  SetToolbar.Invoke();
              }
              

              主活动

              YOURPAGE.SetToolbar = () =>
              {
                  Android.Support.V7.Widget.Toolbar toolbar = 
                      this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
                  SetSupportActionBar(toolbar);
              };
              

              【讨论】:

                【解决方案11】:

                我使用Prism libray 并处理后退按钮/操作,我在我的页面上扩展了 Prism 的INavigatedAware 接口并实现了以下方法:

                    public void OnNavigatedFrom(INavigationParameters parameters)
                    {
                        if (parameters.GetNavigationMode() == NavigationMode.Back)
                        {
                            //Your code
                        }
                    }
                
                    public void OnNavigatedTo(INavigationParameters parameters)
                    {
                    }
                

                当用户从导航栏(Android 和 iOS)按下返回按钮以及按下硬件返回按钮(仅适用于 Android)时,会引发方法 OnNavigatedFrom。

                【讨论】:

                  猜你喜欢
                  • 2020-12-21
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-12-03
                  • 2018-12-10
                  • 2018-02-19
                  • 2016-06-06
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多