如何在 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 版本太旧或版本太新,则会出现工具栏为空的错误。如果发生这种情况,我让它工作以制定最后期限的破解方式是这样的。在OnCreate
在MainActivity
:
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 是Page
和 androdAction
是我添加到 Page 类的 Action。我在我的页面上覆盖 OnAppearing 并在该事件期间运行该操作。【参考方案7】:
诀窍是实现您自己的导航页面,该页面继承自NavigationPage
。它有相应的事件Pushed
、Popped
和PoppedToRoot
。
示例实现可能如下所示:
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 中处理/取消导航的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 ScnViewGestures.Forms 在 Xamarin.Forms 中使用 TouchDown 和 TouchUp 事件创建视图