Xamarin逆引きTips
Xamarin.Formsでタッチイベントを処理するには?(iOS/Androidの各種ジェスチャー対応)
iOS/Androidにおけるタップやスワイプなどの各種ジェスチャーを、Xamarin.Formsで処理する方法を解説する。
モバイルアプリでは、タップやスワイプなど、各種のジェスチャーに対応させる必要がある。今回は、Xamarin.Formsで各種のジェスチャーを処理する方法を解説する*1。
- *1 なお本Tipsは、Windows上でVisual Studio 2013を使用してXamarin.Forms開発をすることを前提としている(※編集部注: Mac上のXamarin Studioでも同様の手順で、本稿の内容が実現できることは確認している)。使用しているXamarin.Formsのバージョンは、プロジェクト作成時に利用されている「1.3.1.6296」である。
1. シナリオ
最初に、画面にImage
ビューを配置し、これをタップしたときのイベントを処理する。続いて、Xamarin.Formsで処理できないイベントであるロングタップ(長押し)を、レンダラーの実装によって取り扱う方法を解説する。
2 .Xamarin.Formsプロジェクトを作成する
メニューバーの[ファイル]-[新規作成]-[プロジェクト]から表示したダイアログで、[テンプレート]-[Visual C#]-[Mobile Apps]-[Blank App (Xamarin.Forms Portable)]を選択し、名前を「GestureSample」として[OK]ボタンを押す(図1)。
3. Imageビューの表示
共通プロジェクトに「Images」フォルダーを作成し、表示する画像(本稿のサンプルでは「image01.png」)をその中にコピーする。
【今回使用した素材画像】プロ生ちゃん
サンプルで使用させていただいた画像は、「プロ生」で公開されている壁紙である。
「プロ生」では、ガイドラインに従うことで、素材の利用が可能である。
続いて、画像ファイルの[プロパティ]で、[ビルド アクション]を「埋め込まれたリソース」に変更する(図3)。
画面にImage
ビューを表示するには、App.cs
ファイルを以下のように修正する。
namespace GestureSample{
public class App : Application{
public App(){
MainPage = new MyPage();
}
……省略……
}
internal class MyPage : ContentPage {
public MyPage() {
var image = new Image { // ←1
HeightRequest = 200,
Source = ImageSource.FromResource("GestureSample.Images.image01.png") //2
};
Content = new StackLayout { // ←3
//iOSで上余白を確保
Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0),
Children = {image}
};
}
}
}
|
Source
プロパティに画像リソースを指定してImage
ビューを作成する(1)。
共有プロジェクトに置いたリソースは、2のようにImageSource.FromResource()
メソッドで取得できる。このとき、リソース名は、「<プロジェクト名>.<フォルダー名>.<画像ファイル名>」である。
ビューにはスタックレイアウト(StackLayout
)を配置し、そこに画像イメージを表示した(3)。
このコードを実行すると次の画面のようになる。
4. タップ
Xamarin.Formsには、TapGestureRecognizer
というクラスがあり、これをビューのGestureRecognizers
コレクションに追加することで、当該ビューで検出したタップを簡単に取得できる。
タップイベントを処理するには、App.cs
ファイルを以下のように修正する。
internal class MyPage : ContentPage {
public MyPage() {
var image = new Image{
HeightRequest = 200,
Source = ImageSource.FromResource("GestureSample.Images.image01.png")
};
var gr = new TapGestureRecognizer(); // ←1
gr.Tapped += (s, e) => {
DisplayAlert("", "Tap", "OK"); //←2
};
image.GestureRecognizers.Add(gr); //←3
Content = new StackLayout {
Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0),
Children = {image}
};
}
}
|
最初に、TapGestureRecognizer
オブジェクトを作成する(1)。
TapGestureRecognizer
オブジェクトのTapped
イベントで、アラートダイアログを表示する(2)。
続いてImage
ビューのGestureRecognizers
コレクションに、作成したTapGestureRecognizer
オブジェクトを追加する(3)。
このコードを実行して、Image
ビューをタップすると次のような画面になる。
【コラム】AndroidにおけるNumberOfTapsRequiredのバグ
TapGestureRecognizer
クラスには、検出対象とするタップ回数が指定できるNumberOfTapsRequired
プロパティがある。これを利用すると、次のようなコードで簡単にダブルタップが処理できるはずである。
var gr = new TapGestureRecognizer();
gr.NumberOfTapsRequired = 2; // 2回のタップを検出対象にする
gr.Tapped += (s, e) => {
DisplayAlert("", "Double Tap", "OK");
};
|
実際、iOSで、このコードは正常に動作する。しかし残念ながら、Androidでは、現在、このコードは正常に動作しない。
この問題は、Xamarin TeamのCraig Dunn氏も「すでにバグとして認識している」と発言しているが、まだ修正はされていない(※2015年2月9日時点での最新stableであるXamarin.Forms 1.3.3.6323でも、修正されていないことを確認している)。
5. ロングタップ
実は、TapGestureRecognizer
クラスで取得できるイベントは、タップのみである。Xamarin.Formsで、その他のジェスチャーなどに対応するためには、レンダラーを記述するしか方法はない。
レンダラーを記述するために必要な最初の作業は、Image
ビューを継承した拡張クラス(本稿の例ではExImage
クラス)の作成である。GestureSampleプロジェクトに、ExImage.cs
ファイルを追加し、以下のコードのように実装する。
using System;
using Xamarin.Forms;
namespace GestureSample {
public class ExImage : Image { // ←1
public event EventHandler LongPress; // ← 2
public void OnLongPress() { // ← 3
if (LongPress != null) {
LongPress(this, new EventArgs());
}
}
}
}
|
ExImage
クラスは、Image
クラスを継承して作成する(1)。
2では、ロングタップのイベントを定義する。
また、publicでOnLongPress
メソッドを定義しておき(3)、実際にコントロールでイベントが発生したときに、これを呼び出すようにする。
続いて、この拡張クラスを使用するようにApp.cs
ファイルを修正する。
using Xamarin.Forms;
namespace GestureSample {
……省略……
internal class MyPage : ContentPage {
public MyPage() {
var exImage = new ExImage { // ←1
HeightRequest = 200,
Source = ImageSource.FromResource("GestureSample.Images.image01.png")
};
exImage.LongPress += (s, a) => { // ←2
DisplayAlert("", "Long Press", "OK");
};
Content = new StackLayout {
Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0), //iOSで上余白を確保
Children = {exImage
};
}
}
}
|
Image
ビューは、拡張したExImage
クラスを使用するように変更した(1)。
また、拡張クラスで新たに定義したLongPress
イベントでアラートを表示するようにした(2)。
その他は、タップのときのコードと同じである。
6. レンダラーの実装(iOS)
続いて、レンダラーの実装を行う。
GestureSample.iOSプロジェクトに、ExImageRenderer.cs
ファイルを追加し、以下のコードを記述する。
using GestureSample;
using GestureSample.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ExImage), typeof(ExImageRenderer))] // ←1
namespace GestureSample.iOS {
internal class ExImageRenderer : ImageRenderer { // ← 2
protected override void OnElementChanged(ElementChangedEventArgs<Image> e) {
base.OnElementChanged(e);
var exImage = Element as ExImage;
var gr = new UILongPressGestureRecognizer(o => exImage.OnLongPress()); // 3
AddGestureRecognizer(gr); // ← 4
}
}
}
|
1は、レンダラーを記述する場合の定型句である。ExportRenderer
属性によって、「ExImage
コントロールの描画にはExImageRenderer
クラスを使用する」と定義している。
Image
ビューのレンダラークラスはImageRenderer
であるので、ExImageRenderer
クラスは、これを継承している(2)。
iOSでのロングタップ検出は、UILongPressGestureRecognizer
というクラスのインスタンスを生成して(3)、レンダラーのAddGestureRecognizer
メソッドでそれをコントロールに登録する(4)という作業になる。
より詳しく説明すると、3で、UILongPressGestureRecognizer
クラスのインスタンスを生成している。そのコンストラクターのメソッド引数に指定しているメソッド(ラムダ式)は、ロングタップを認識(Recognize)すると呼び出される。その中で、先ほど定義したExImage
コントロールのOnLongPress
メソッドを呼び出している。
4では、このUILongPressGestureRecognizer
オブジェクトを、コントロール自身に登録している。
このコードを実行して、Imageビューをロングタップすると、次のような画面になる。
7. レンダラーの実装(Android)
Androidでのロングタップの検出は、iOSと比べるとやや複雑になっている。具体的には、リスナークラスを定義して、これをジャスチャーディテクター経由でコントロール上のイベントにひも付ける作業になる。
それでは、順に手順を見ていこう。
リスナークラスは、GestureSample.Droidプロジェクトに、MyGestureListener.cs
ファイルを追加し、以下のように実装する。
using Android.Views;
namespace GestureSample.Droid {
internal class MyGestureListener : GestureDetector.SimpleOnGestureListener { //1
public ExImage ExImage { private get; set; } // ← 2
public override void OnLongPress(MotionEvent e) { // ← 3
base.OnLongPress(e);
if (ExImage != null) {
ExImage.OnLongPress(); // ← 4
}
}
}
}
|
リスナークラスは、GestureDetector.SimpleOnGestureListener
クラスを継承して作成する(1)。
リスナークラスのExImage
プロパティは、外から設定可能な拡張イメージへのポインターである(2)。
GestureDetector.SimpleOnGestureListener
クラスは、各種のジェスチャーをハンドルするOn
で始まるメソッドを持っており、今回は、ロングタップ時に呼ばれるOnLongPress
メソッドをオーバーライドして(3)、そこからExImage
オブジェクトのOnLongPress
メソッドを呼び出した(4)。
続いて、レンダラークラスを定義する。GestureSample.Droidプロジェクトに、ExImageRenderer.cs
ファイルを追加し、以下のように実装する。
using Android.Views;
using GestureSample;
using GestureSample.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ExImage), typeof(ExImageRenderer))]
namespace GestureSample.Droid {
internal class ExImageRenderer : ImageRenderer {
private readonly MyGestureListener _listener; // ← 1
private readonly GestureDetector _detector; // ← 2
public ExImageRenderer() { // ←3
_listener = new MyGestureListener();
_detector = new GestureDetector(_listener);
}
protected override void OnElementChanged(ElementChangedEventArgs<Image> e) {
base.OnElementChanged(e);
_listener.ExImage = Element as ExImage; // ← 4
GenericMotion += (s, a) => _detector.OnTouchEvent(a.Event); // ← 5
Touch += (s, a) => _detector.OnTouchEvent(a.Event); // ← 6
}
}
}
|
最初に、先ほど作成したMyGestureListener
クラス(1)と、これを関連付けるために使用するGestureDetector
クラス(2)を定義し、レンダラークラス(ExImageRenderer
クラス)のコンストラクターで、これらのインスタンスを生成する(3)。
リスナークラスのExImage
プロパティは、OnElementChanged
メソッド内で初期化される(4)。
ひも付けは、GenericMotion
イベントとTouch
イベントに対して行う(56)。実は、ロングタップだけなら、Touch
イベントだけでよいのだが、今後の拡張のために、ここでは両方のイベントに同じようにひも付けを行っておく。
その他、レンダラーとしての実装は、iOSのものと同じである。
このコードを実行して、Imageビューをロングタップすると、次のような画面になる。
8. その他のジェスチャー
今回、iOSで使用した、UILongPressGestureRecognizer
クラスは、ロングプレスを検出するためのものであるが、これを、
UITapGestureRecognizer
(タップ)UIPinchGestureRecognizer
(ピンチ)UIPanGestureRecognizer
(パン・ドラッグ)UISwipeGestureRecognizer
(スワイプ)UIRotationGestureRecognizer
(ローテイト)
などのクラスに置き換えれば、それぞれのジャスチャーを処理できる。
またAndroidでは、リスナークラスでOnLongPress
メソッドをオーバーライドしたが、GestureDetector.SimpleOnGestureListener
では、この他にも、
OnDown
(押下)OnShowPress
(押下[押してすぐに動かすと呼ばれない])OnLongPress
(長押し)OnFling
(フリック)OnScroll
(スクロール)OnSingleTapUp
(シングルタップ[ダブルタップ時も呼ばれる])OnSingleTapConfirmed
(シングルタップ[ダブルタップ時は呼ばれない])OnDoubleTap
(ダブルタップ)OnDoubleTapEvent
(ダブルタップ [押す・動かす・離す] )
などのメソッドが、オーバーライド可能である。
これらを使用することで、各プラットフォームで処理できるイベントは、全てXamarin.Formsでも利用可能となる。
9. まとめ
今回は、Xamarin.Formsでタップおよびロングタップを処理する方法を紹介した。また、レンダラーを実装することで、その他のジェスチャーも全て処理可能であることを示した。
しかし、各プラットフォームでのジャスチャーなどの扱いは、決して共通化されているわけではない。そのため、Xamarin.Formsから全てを同じように扱うとなると、いろいろと解決すべき問題が残る。この辺が、Xamarin.Formsで、現在タップしか扱われていない理由なのかもしれない。
※以下では、本稿の前後を合わせて5回分(第33回~第37回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
33. Xamarin Studio/Visual Studioで「Ricty Diminished」プログラミング用フォントを使うには?
「Ricty Diminished」や「Source Code Pro」などのプログラミング用フォントを、Xamarin Studio/Visual Studioのコードエディターのフォントとして設定する方法。
34. Xamarin.FormsでBoxViewコントロールを拡張するには?
四角形を描画するBoxViewコントロールを拡張してネイティブ側で描画することで、角丸・枠線・影付きなどを実現する方法を説明する。
35. 【現在、表示中】≫ Xamarin.Formsでタッチイベントを処理するには?(iOS/Androidの各種ジェスチャー対応)
iOS/Androidにおけるタップやスワイプなどの各種ジェスチャーを、Xamarin.Formsで処理する方法を解説する。
36. Xamarin.Formsでツールバーアイテムによるメニューを設置するには?
PageクラスのToolbarItemsプロパティを使って、画面の上部にツールバー(Android)/ナビゲーションバー(iOS)を表示する方法を解説する。
37. MvvmCrossのプロジェクトをセットアップするには?
クロスプラットフォーム開発を支援するXamarin用ライブラリの「MvvmCross」を使ってiOS/Androidアプリ開発を行うためのプロジェクトの作成方法を説明する。