最近开发Xamarin App 遇到一个需求就是当用户在当前页面编辑数据后,点击返回(离开当前页面的时候)需要判断数据有变更需要提示框“Would you like to save this changes?”
由于Android 的OnBackButtonPress方法只支持物理返回键调用,而导航栏左上角的返回是不会触发这个事件的、又不想用模式窗体方式来处理,因为编辑页面有选择图片的功能,模式窗体会导致不可选择手机相册的图片。
废话不多说,直接干就完了!
先定义一个BaseContentPage
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace BackButtonDemo
{
public class BaseContentPage:ContentPage
{
/// <summary>
/// Gets or Sets the Back button click overriden custom action
/// </summary>
public Action CustomBackButtonAction { get; set; }
public static readonly BindableProperty EnableBackButtonOverrideProperty =
BindableProperty.Create(
nameof(EnableBackButtonOverride),
typeof(bool),
typeof(BaseContentPage),
false);
/// <summary>
/// Gets or Sets Custom Back button overriding state
/// </summary>
public bool EnableBackButtonOverride
{
get
{
return (bool)GetValue(EnableBackButtonOverrideProperty);
}
set
{
SetValue(EnableBackButtonOverrideProperty, value);
}
}
}
}
MainPage跳转到Page2
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace BackButtonDemo
{
// Learn more about making custom code visible in the Xamarin.Forms previewer
// by visiting https://aka.ms/xamarinforms-previewer
[DesignTimeVisible(false)]
public partial class MainPage : BaseContentPage
{
public MainPage()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new Page2());
}
}
}
配置EnableBackButtonOvereide=true:
<backbuttondemo:BaseContentPage
xmlns:backbuttondemo="clr-namespace:BackButtonDemo"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" EnableBackButtonOverride="True"
x:Class="BackButtonDemo.Page2">
<ContentPage.Content>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
</backbuttondemo:BaseContentPage>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace BackButtonDemo
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Page2 : BaseContentPage
{
public Page2()
{
InitializeComponent();
if (EnableBackButtonOverride)
{
this.CustomBackButtonAction = async () =>
{
var result = await this.DisplayAlert(null,
"Hey wait now! are you sure " +
"you want to go back?",
"Yes go back", "No");
if (result)
{
await Navigation.PopAsync(true);
}
};
}
}
}
}
For Android:
在MainActivity增加配置如下:
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
//非常重要,缺少这行代码导致不响应OnOptionItemSelected
Android.Support.V7.Widget.Toolbar toolbar
= this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
}
还需要捕抓导航栏返回菜单事件,也是在MainActivity.cs中
//capture navigation bar back button selected event
public override bool OnOptionsItemSelected(IMenuItem item)
{
// check if the current item id
// is equals to the back button id
if (item.ItemId == 16908332) // xam forms nav bar back button id
{
// retrieve the current xamarin
// forms page instance
var currentpage = (BaseContentPage)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);
}
}
//android Hardware back button event
public override void OnBackPressed()
{
// this is really not necessary, but in Android user has both Nav bar back button
// and physical back button, so its safe to cover the both events
var currentpage = (BaseContentPage)Xamarin.Forms.Application.Current.
MainPage.Navigation.NavigationStack.LastOrDefault();
if (currentpage?.CustomBackButtonAction != null)
{
currentpage?.CustomBackButtonAction.Invoke();
}
else
{
base.OnBackPressed();
}
}
For IOS:
需要修改Page呈现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BackButtonDemo;
using BackButtonDemo.iOS.CustomerRenderer;
using CoreGraphics;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(BaseContentPage), typeof(BaseContentPageRenderer))]
namespace BackButtonDemo.iOS.CustomerRenderer
{
public class BaseContentPageRenderer: PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (((BaseContentPage)Element).EnableBackButtonOverride)
{
SetCustomBackButton();
}
}
private void SetCustomBackButton()
{
// Load the Back arrow Image
//"iosbackarrow.png" is resouce file
var backBtnImage = UIImage.FromBundle("iosbackarrow.png");
backBtnImage =
backBtnImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
// Create our Button and set Edge Insets for Title and Image
var backBtn = new UIButton(UIButtonType.Custom)
{
HorizontalAlignment = UIControlContentHorizontalAlignment.Left,
TitleEdgeInsets = new UIEdgeInsets(11.5f, 15f, 10f, 0f),
ImageEdgeInsets = new UIEdgeInsets(1f, 8f, 0f, 0f)
};
// Set the styling for Title
// You could set any Text as you wish here
backBtn.SetTitle("Back", UIControlState.Normal);
// use the default blue color in ios back button text
backBtn.SetTitleColor(UIColor.White, UIControlState.Normal);
backBtn.SetTitleColor(UIColor.LightGray, UIControlState.Highlighted);
backBtn.Font = UIFont.FromName("HelveticaNeue", (nfloat)17);
// Set the Image to the button
backBtn.SetImage(backBtnImage, UIControlState.Normal);
// Allow the button to Size itself
backBtn.SizeToFit();
// Add the Custom Click event you would like to
// execute upon the Back button click
backBtn.TouchDown += (sender, e) =>
{
// Whatever your custom back button click handling
if (((BaseContentPage)Element)?.CustomBackButtonAction != null)
{
((BaseContentPage)Element)?.CustomBackButtonAction.Invoke();
}
};
//Set the frame of the button
backBtn.Frame = new CGRect(
0,
0,
UIScreen.MainScreen.Bounds.Width / 4,
NavigationController.NavigationBar.Frame.Height);
// Add our button to a container
var btnContainer = new UIView(
new CGRect(0, 0, backBtn.Frame.Width, backBtn.Frame.Height));
btnContainer.AddSubview(backBtn);
// A dummy button item to push our custom back button to
// the edge of screen (sort of a hack)
var fixedSpace = new UIBarButtonItem(UIBarButtonSystemItem.FixedSpace)
{
Width = -16f
};
// wrap our custom back button with a UIBarButtonItem
var backButtonItem = new UIBarButtonItem("", UIBarButtonItemStyle.Plain, null)
{
CustomView = backBtn
};
// Add it to the ViewController
NavigationController.TopViewController.NavigationItem.LeftBarButtonItems
= new[] { fixedSpace, backButtonItem };
}
}
}