如何 handle/cancel 在 Xamarin Forms 中返回导航

How to handle/cancel back navigation in Xamarin Forms

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

你是对的,如果你想阻止导航,在你的页面 class 覆盖 OnBackButtonPressed 和 return true。它对我来说很好,我有相同的版本。

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

取决于您要查找的内容(如果您只是想取消后退按钮导航,我不建议使用此选项),OnDisappearing 可能是另一种选择:

protected override void OnDisappearing()
{
       //back button logic here
}
 protected override bool OnBackButtonPressed()
        {
            base.OnBackButtonPressed();
                return true;
        }

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

OnBackButtonPressed() 这将在按下硬件后退按钮时调用,如 android 中所示。 这不适用于按 ios 中的软件后退按钮。

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

  1. 使用 NavigationPage.ShowHasBackButton(this, false) 隐藏 NavigationPage 后退按钮并推送具有自定义 Back/Cancel/Close 按钮的模态页面

  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);
        }           
    }

好吧,几个小时后我弄明白了。它分为三个部分。

#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 class 制作它,它实现了 ContentPage 。所以在我的 MyAdvantagePage class 我添加了

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 的导航。

诀窍是实现您自己的继承自 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();
}

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

导航页面可以这样创建:

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

MainPage 将是要显示的根页面。

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

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

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
}

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

对于你的情况,我会简单地

  • 创建具有不透明背景的完整页面弹出窗口
  • 在 ⚠️ParentPage⚠️ 上为 Android 覆盖 ↩️ OnBackButtonPressed,像这样:

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

由于 back-button 影响通常的 NavigationStack,只要用户尝试使用它,您的 parent 就会弹出,而您的 "popup is showing".

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

这些目标的问题已解决

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

凯尔回答的补充 集

在您的页面内

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);
};

我使用 Prism 库并处理背面 button/action 我在我的页面上扩展了 Prism 的 INavigatedAware 接口并实现了这个方法:

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

    public void OnNavigatedTo(INavigationParameters parameters)
    {
    }

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