スワイプを検知するには(AndroidMode編)

◆PROCESSING 逆引きリファレンス

 カテゴリー:スマホ(AndroidMode)

スワイプを検知するには(AndroidMode編)

【概要】

PROCESSINGにAndroidMode を導入する事で、PROCESSINGで開発したプログラムをAndroid端末上で動かす事ができるようになります。

AndroidModeの導入については「PROCESSINGをAndroid端末で動かすには(4.0版)」記事を参照してください。

スワイプとは画面をタップした状態で、そのまま指を離さずに、一定方向へゆっくりと滑らせて移動する操作です。画面の切り替え操作などに利用しますね。

応用例として、タッチアンドホールド(長押し)した状態から、指を離さずに滑らせる操作を「ドラッグ」と呼ぶ事があります。

(画像URL:illust-AC 様:かみたまさん、amiyaoさん)

AndroidModeで画面のスワイプを検知するには、以下の2種類の方法があります。

  • タッチ系イベント関数を利用する
  • surfaceTouchEvent 関数を利用する

タッチ系イベント関数で指の移動を検知するのは、touchMoved 関数になります。

タッチ系イベント関数には、これ以外にもタップの開始を検知する touchStarted や終了を検知するtouchEnded があります。詳しくは「タップを検知するには(AndroidMode編)」記事を参照して下さい。

surfaceTouchEvent 関数はタッチ系イベント関数のように、操作毎に処理が分かれておらず、指の移動を含めた全てのイベントがこの関数に通知されてきます。

surfaceTouchEvent は、タッチ系イベント関数に比べて詳細な情報を得ることができます。

またダブルタップやフリックなど、より高度な操作を検知したい場合も、surfaceTouchEvent 関数を利用する事になります。

マルチタップなどを考慮するなら、タッチ系イベント関数よりも surfaceTouchEvent 関数を利用する事をオススメします。

 

【詳細】

タッチ系イベントで移動を知る

移動イベントvoid touchMoved ( TouchEvent  touchEvent ) ;

touchEvent : イベント内容

画面上で指を動かした際に呼び出されます。シングルタップ、マルチタップのどちらでも、同じイベント関数が呼ばれます。

渡されてくる TouchEvent  クラスのインスタンス変数を利用する事で、移動した指の数や座標を知ることができます。

TouchEvent から得るint tapNum = touchEvent . getNumPointers( ) ;
float x = touchEvent . getPointer( int index ).x ;
float y = touchEvent . getPointer( int index ).y ;

tapNum :移動中の指の情報数
index : 何番目の情報かを示す指標(0以上 tapNum-1 まで)
x : 移動した指の横座標
y : 移動した指の縦座標

TouchEvent には様々なメソッドやプロパティがありますが、よく使うのは上記3つではないかと思います。

getNumPointers が1ならシングルタップ、2以上ならマルチタップされたという事です。

getNumPointers でタップされている指の数(渡されてくる Pointer 情報の数)を取得し、該当Pointerからタップされている座標を拾う形になります。

touchMoved イベントは指が動いた時に発生するイベントでしかないので、それがスワイプなのかフリックなのかは判別できません。

またタップしたつもりが、微妙に指が動いていてしまった場合も呼び出されてくるため、touchMoved で正確なジェスチャーを拾う事は困難です。

ただ指が動いたという事を検知したいだけなら十分ですが、詳細なジェスチャーを知りたい場合は、surfaceTouchEvent  を利用しましょう。

 

surfaceTouchEventで移動を知る

イベントboolean surfaceTouchEvent( MotionEvent motionEvent ) ;

motionEvent : イベント内容

渡されてくる情報はTouchEvent  ではなく、MotionEvent クラスのインスタンス変数です。

MotionEvent は、Android 標準のonTouchEventに渡されてくるものと同じものになります。

MotionEvent から情報を得る(1)int pcount = motionEvent . getPointerCount() ;
int pIndex = motionEvent . getActionIndex() ;
int eAction= motionEvent . getActionMasked() ;
float x = motionEvent . getX( int pIndex ) ;
float y = motionEvent . getY( int pIndex ) ;

pcount: Pointer 配列数(タップされている指の数)
pIndex : イベントが発生したPointer配列の位置(0 ~ pcount – 1 まで)
eAction: イベントの種類
x : タップされた横座標
y : タップされた縦座標

getPointerCount で渡される数は、タップされている指の数(Pointer情報の数)です。マルチタップが行われると、getPointerCount は2以上の数を戻します。

surfaceTouchEvent  は指を動かした時だけでなく、タップしたり指を離した時にも呼び出されます。

ですのでどんな操作が行われたかを知るには、 getActionMasked 命令を使って自分で判定する必要があります。

指を動かした時に発生する Action は  MotionEvent.ACTION_MOVE (2)  になります。

 

履歴を得るint histNum = motionEvent.getHistorySize() ;
float hx = motionEvent.getHistoricalX( int pos, int hpos );
float hy = motionEvent.getHistoricalY( int pos, int hpos );

histNum : 履歴の数
pos : イベントが発生したPointer配列の位置(0 ~ pcount – 1 まで)
hpos : 履歴の位置(0 ~ histNum – 1 まで)
hx : 履歴のタップされた横座標
hy : 履歴のタップされた縦座標

ACTION_MOVE イベントで理解しておいて欲しい事は、指が動かされた時に、surfaceTouchEvent がタイムリーに呼び出されるわけではないという事です。

指を素早く動かすと、各移動情報がOS内部に蓄積され、OSの都合が良いタイミングで surfaceTouchEvent が呼び出されます。

(画像URL:illust-AC 様:かみたまさん、casa4さん)

例えば上図では、 B と C の情報は指が移動した時にタイムリーには通知されず、Dの情報と一緒に通知されてきます。

この蓄積された情報数を知るのが getHistorySize() 命令です。また蓄積された情報から座標を取得するには、getHistoricalX()、getHistoricalY() 命令を利用します。

蓄積情報は添字の若い方(0)が最も古い(早く蓄積された)情報になります。また蓄積情報よりも Pointer情報の方が新しい情報になります。

たとえば上記のような感じです。

 

surfaceTouchEventでスワイプを知る

さて前置きが長くなりましたが、実はここまでなら surfaceTouchEvent とtouchMoved は大差がありません。

つまり指が動いたことはわかっても、それがスワイプなのかフリックなのかを判別する事は困難です。

本関数の使い所は、GestureDetector クラスの onTouchEvent 関数を呼び出し、GestureDetector クラスに様々なジェスチャーを判定させて、OnGestureListener がもつ対応イベント関数を呼び出すことにあります。

GestureDetector クラスを利用すると、スワイプ以外にも様々なジェスチャーを検知できるようになります。

 

GestureDetectorを生成するGestureDetector ges = new GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler);

ges : GestureDetector クラスのインスタンス変数
listener : OnGestureListener インタフェースを実装したクラスのインスタンス
context : Application Context
handler : UIスレッドのLooperと連携したHandlerインスタンス

GestureDetector クラスを利用するには、まずインスタンスを作成する必要があります。

インスタンス作成時には、GestureDetector クラスのコンストラクタにOnGestureListener インタフェースを実装したリスナークラスのインスタンスを与える必要があります。

OnGestureListenerインスタンスを実装したクラスではなく、GestureDetector.SimpleOnGestureListener クラスを継承したクラスを用意して、そのクラスのインスタンスを与えてもOKです。

下記サンプルプログラムでは、標準エディタからコーディングする事と、いろいろなジェスチャーをキャッチする事を想定して、SimpleOnGestureListener クラスを継承した専用のクラスを用意しています。

で、少し話は難しくなるのですが・・・GestureDetector クラスをsetup()関数から作成しようとすると
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
例外が発生します。

これはGestureDetector クラスが、タップに関連するイベントを処理するための Handler を、内部的に生成するためです。

Handlerには、Handlerが処理した結果を受け取る「対」となる存在(Looper)が必要です。ところが、(特に何も指定しなければ)LooperはUIスレッドにしか存在しません。

setup() 関数は、バックグラウンドスレッドで動作しています。そしてバックグラウンドスレッドには、Looperは存在しないのです。

解決策は2通りあります。

  • UIスレッドでGestureDetector クラスを作成する
  • UIスレッドのLooperへリクエストを送信するHandlerを生成して、GestureDetector のコンストラクタに与える

ここでは2番めの方法を採用します。

以下のような感じです。

(画像URL:illust-AC 様:かみたまさん、casa4さん)

理屈は複雑ですが、コーディングはシンプルです(笑)。

ポイントになる処理だけを抜き出すと、こんな感じになります。

Looperや、Handler、UIスレッドについて興味がある方は、以下のサイト様などを参照してみてください。

 

タップイベントを通知するboolean gestureDetector.onTouchEvent( MotionEvent motionEvent );

gestureDetector : GestureDetector インスタンス
motionEvent : surfaceTouchEventに渡されたイベント

スワイプを検知するには、作成したGestureDetector インスタンスに、発生したタップイベントを通知する必要があります。

GestureDetector インスタンスにタップイベントを通知するには、surfaceTouchEvent関数から、GestureDetectorクラスが持つ onTouchEvent を呼び出して下さい。

 

スワイプイベントboolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) ;

e1 : イベントが最初に発生した位置情報(起点)
e2 : 現在のイベント位置
distanceX : 横方向の移動距離
distanceY : 縦方向の移動距離

さてここまで準備ができたら、SimpleOnGestureListenerを継承したクラスで、スワイプを検知するために 、onScroll 関数をオーバライドします。

distanceX、distanceY に渡されてくる移動距離は、起点(e1)から現在のイベント位置(e2)までの距離ではありません。そうではなく、前回 onScroll 関数が呼び出された場所(前回のe2)から「どれだけ動いたか」を示す情報になります。

(画像URL:illust-AC 様:かみたまさん、casa4さん)

distanceX、distanceY は移動方向によって正負が変わります。

(画像URL:illust-AC 様:かみたまさん)

distanceX、distanceY の正負をみれば、指を滑らせた方向がわかります。またdistanceX、distanceY を使って三平方の定理を計算する事で、前回イベントからの移動距離が求まります。

 

【関連記事】

 


サンプルプログラム

タッチイベントを利用する例:

touchMovedイベントで指が動いた座標を全て拾い、画面に青丸を描いています。

<出力サンプル>

 

スワイプを検知する例:

onScroll イベントに渡されるdistanceX、distanceY を利用して、上下左右斜めの8方向のうち、どちらにスワイプされたかを判定し、矢印画像を回転させています。

FIRE_DISTANCE定数を変更する事で、スワイプの感度を調整できます。

なお画像の回転方法については「画像を回転するには」記事を参照してください。

<出力サンプル>

(画像URL:illust-AC 様:anny さん)

 


PROCESSING逆引きリファレンス一覧 へ戻る

本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。