LoginSignup
7
6

More than 5 years have passed since last update.

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

Posted at

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へ遷移するだけ。

Pages.cs
// 画面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 を親へ伝搬する。

MainActivity.cs
[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 に設定する。

MyPageRenderer.cs
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 を取得できないしで使えないじゃん。。。

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

7
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6