如何 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 中的软件后退按钮。
对于仍在与此问题作斗争的任何人 - 基本上您无法跨平台拦截后退导航。话说有两种方法可以有效解决问题:
使用 NavigationPage.ShowHasBackButton(this, false)
隐藏 NavigationPage 后退按钮并推送具有自定义 Back/Cancel/Close 按钮的模态页面
拦截每个平台的后退导航。这是一篇针对 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 版本太旧或版本太新,则会出现工具栏为空的错误。如果发生这种情况,我让它在最后期限前工作的黑客攻击方式就是这样。在 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
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
的导航页面。它具有适当的事件 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();
}
此接口可以在所有页面的基础 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。
我试图通过覆盖 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 中的软件后退按钮。
对于仍在与此问题作斗争的任何人 - 基本上您无法跨平台拦截后退导航。话说有两种方法可以有效解决问题:
使用
NavigationPage.ShowHasBackButton(this, false)
隐藏 NavigationPage 后退按钮并推送具有自定义 Back/Cancel/Close 按钮的模态页面拦截每个平台的后退导航。这是一篇针对 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 版本太旧或版本太新,则会出现工具栏为空的错误。如果发生这种情况,我让它在最后期限前工作的黑客攻击方式就是这样。在 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
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
的导航页面。它具有适当的事件 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();
}
此接口可以在所有页面的基础 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。