Experiments Never Fail

Xamarin.Forms、Android での BACK キーの制御

Xamarin.Forms でどうにかしたい iOS と Android の違い の「BACKキーの制御」の 現時点(1.1.0.6201) での回答。

Android の BACKキーの制御を、Xamarin.Forms ではどう扱えるかを調べた。

シナリオ #

Xamarin.Forms による画面1(MainPage)、2(SecondPage)があり、MainPage では BACKキーで戻る(=アプリ終了)事ができるが、SecondPage ではBACKキーが効かない、ようにしたい。

対策 #

まず画面1と2はこんな感じ。ボタンを押したら画面2へ遷移するだけ。

// 画面1
public class MainPage : ContentPage
{
public MainPage()
{
var button = new Button
{
Text = "To Second",
VerticalOptions = LayoutOptions.Center,
};

button.Clicked += (sender, e) =>
{
this.Navigation.PushAsync(new SecondPage());
};

Content = button;
}
}

// 画面2
public class SecondPage : ContentPage
{
public SecondPage()
{
Content = new Label
{
Text = "Second"
};
}
}

ここからが本題。
まず Android側のエントリポイントである MainActivity.cs は以下のように、ContentPage プロパティを設ける。そして OnBackPressed メソッドを override して、MainPage だったら OnBackPressed を親へ伝搬する。

[Activity(Label = "ScrollTest.Android.Android", MainLauncher = true)]
public class MainActivity : AndroidActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);

Xamarin.Forms.Forms.Init(this, bundle);

SetPage(new NavigationPage(new MainPage()));
}

internal Page ContentPage
{
get;
set;
}

public override void OnBackPressed()
{
if (this.ContentPage is MainPage)
{
base.OnBackPressed();
}
}
}

次に、MainActivity.ContentPage への設定を行うコードは以下の通り。
PageRenderer を拡張して ExportRenderer することで、すべての Page にフックをかけ、Page の表示時に MainActivity.ContentPage に設定する。

using System;
using Xamarin.Forms.Platform.Android;
using Android.App;
using Xamarin.Forms;
using ScrollTest.Android;
using Android.Views;
using Android.Graphics;

[assembly:ExportRenderer(typeof(ContentPage), typeof(MyPageRenderer))]

namespace ScrollTest.Android
{
public class MyPageRenderer : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);

// なんとなく不安なので weak にしてみた
var activity = new WeakReference<MainActivity>(this.Context as MainActivity);

e.NewElement.Appearing += (_, __) =>
{
MainActivity a;
if (activity.TryGetTarget(out a)) {
a.ContentPage = e.NewElement;
}
};
}
}
}

これで、画面1(MainPage)の時だけ BACKキーが効くようにできる。

Appearing イベントが必要なの? #

Xamarin.Forms の Android実装では、画面遷移の度に 「同じインスタンスの MainActivity」 が使いまわされる、さらに OnElementChanged は、各Pageにつき1度しか発生しない。その為、画面1→2→1と遷移すると MainActivity.ContentPageSecondPage のままになってしまう。ので Appearing イベントで表示の度に MainActivity.ContentPage を設定する必要がある。

AndroidActivity に static な BackPressed イベントがあるんだけど… #

イベントハンドラの定義は
public delegate bool BackButtonPressedEventHandler(object sender, EventArgs e);
となっていて、true を返すと BACK キーを無効にできるようなのだけど、senderMainActivityだし、EventArgs は Page を取得できないしで使えないじゃん。。。

なんだかすごく発展途上な気がする、その内いろいろ整備されそうなので、それまで待った方が良い気がします。。。

published at tags: Xamarin C# Android