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)。
![図3 [ビルド アクション]を「埋め込まれたリソース」に変更](https:///re.buildinsider.net/mobile/xamarintips/0035/002.gif)
画面に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アプリ開発を行うためのプロジェクトの作成方法を説明する。