如何在 Xamarin Forms 中处理/取消导航

Posted

技术标签:

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

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

【问题讨论】:

我相信该事件是专门针对 android 硬件后退按钮的,而不是一般的“后退”导航事件。 我知道。我尝试了 Android 和硬件后退按钮,没有被调用 【参考方案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】:

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

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

    为每个平台本机拦截后退导航。这是一篇适用于 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。

【讨论】:

以上是关于如何在 Xamarin Forms 中处理/取消导航的主要内容,如果未能解决你的问题,请参考以下文章

内存泄漏处理Xamarin.Forms

如何使用 ScnViewGestures.Forms 在 Xamarin.Forms 中使用 TouchDown 和 TouchUp 事件创建视图

Xamarin.Forms Button长按事件

异步事件处理程序有时会挂在 Xamarin.Forms

Xamarin.Forms 中的全局异常处理

Xamarin.Forms 中附加属性的绑定问题