GestureDetectorのまとめ

# はじめに
GestureDetectorを使う時にいっつも忘れてしまう上にググってもまとめてあるページがないしドキュメントも割と分かりにくいのでここにまとめておきます．
ちなみにGestureDetectorとScaleGestureDetectorについて書きます．

# 基本
基本的な使い方

```java:MyView
public class MyView extends View implements View.OnTouchListener, GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener {
    private GestureDetector gesture;
    private ScaleGestureDetector scaleGesture;

    /* この辺にコンストラクタとかを書く(initを呼び出す) */

    private void init(AttributeSet attrs, int defStyle){
        gesture = new GestureDetector(getContext(), this);
        scaleGesture = new ScaleGestureDetector(getContext(), this);
        setOnTouchListener(this);
    }

    public boolean onTouch(View v, MotionEvent event) {
        return event.getPointerCount() == 1 ? gesture.onTouchEvent(event) : scaleGesture.onTouchEvent(event);
    }

    /* 以下Overrideメソッド */
```

1本指ならgestureでそれ以上ならscaleGestureを呼び出してる．
（[参考：GestureDetectorとScaleGestureDetectorを併用する](http://techracho.bpsinc.jp/annotunzdy/2011_11_29/4739)）
ただし，この方法だとScaleのときにちょっと挙動がおかしくなる．
2本指なのにSingleTapUpが呼ばれたりするよりはマシかなーって感じでこっちを使ってる．

# 各メソッドの挙動
以下，各メソッドの挙動について．

## GestureDetector

### onDown
指が画面に押下されたら呼び出される．
とにかく押下されたら呼び出される．
要するにDOWNのイベントを拾ってる．
**基本的に返り値はtrueにしておく**
返さない場合はonSingleTapUpが呼ばれなかったりonLongPressが呼ばれたりとにかく挙動がおかしくなる．
タップされた場所は引数のMotionEventからgetX()とgetY()で取得できる．

### onLongPress
指が画面に押下されてじーっとしていると呼び出される．
**指が離れた時に呼び出されるのではなく，一定時間が経過すると呼び出される**
少しでも移動してしまうと呼び出されない．
移動してからじーっと止まっていても呼び出されない．
setIsLongpressEnabledメソッドを使って無効化できる．
使わないなら無効化しておく方がいい．
タップされた場所は引数のMotionEventからgetX()とgetY()で取得できる．

### onSingleTapUp
指が画面に押下されて，画面から離れると呼び出される．
UPのイベントを拾って呼び出してるイメージ．
ただし，指が少しでも移動してしまう，もしくは長押したときは呼び出されない．
タップされた場所は引数のMotionEventからgetX()とgetY()で取得できる．


### onShowPress
指が画面に押下されて，**少しだけ画面にとどまっていると呼び出される**
指が離れたときに呼び出されるのではない．
移動させずに指を離すとonSingleTapUpが呼び出される．
少しもとどまらずに指を移動させると呼び出されない．
タップされた場所は引数のMotionEventからgetX()とgetY()で取得できる．
使いどころが難しい気がする．
（ドキュメントにはハイライトするときとか書いてあるけど，LongPressで拾うだろうし・・・）

### onScroll
指が画面に押下されて，画面上を移動すると呼び出される．
MOVEイベントを拾っているイメージ．
指が離れたときに呼び出されるメソッドが無いので，**スクロールの開始と終了を管理したい場合は適していない**
そもそも設計としてonScrollが呼ばれたら1回の処理で完結させる方が良いと思う．
MotionEventが2つとfloatが2つ渡される．
第1引数のMotionEventがScrollの起点となる場所，第2引数が現在の場所．
第3引数と第4引数が移動距離を表すfloat値．
**onScrollを呼び出した前回の場所からどれだけ動いたかが格納されている**
起点から現在までの距離では無い（計算すりゃ分かるし）
方向はX軸・Y軸の正方向に指を動かすとマイナスになる．
画面をスクロールさせる場合は，表示している座標にそのまま足せば良い．

### onFling
指で画面上をサッとなぞると呼び出される．
スクロールで加速度をつけたいときとかに使う．
onScrollが呼び出されて**指が離れたときに加速度がついていると判断されると呼び出される**
第1，第2引数はonScrollと一緒，第3，第4引数は速度が入っている．
1秒間に移動するピクセル数．プラスマイナスはonScrollと一緒．


## ScaleGestureDetector
### onScaleBegin
2本指でピンチイン・アウト処理が始まったら呼び出される．
2本指タッチでは呼び出されない．回転操作でも呼び出されない．純粋にピンチイン・ピンチアウトで呼び出される．
他の処理を中断させたりするときに使用．

### onScaleEnd
ピンチイン・アウトの処理が終わったら呼び出される．
UPで呼び出しているイメージ．
指が1本になると呼び出される．3本→2本だと呼び出されない．（当たり前だけど）
で，**上記のようなonTouch実装をしていると1本指に戻った時にちょっと困る**
1本指でスタート→2本指に移行→1本指に戻るだと問題ないけど
2本指でスタート→1本指に移行だと**DOWNの時にGestureDetectorが呼ばれていないせいでGestureDetectorのイベントが発生しない**
要は2本指で拡大縮小したあと，1本指にそのまま移行してスクロールさせてもスクロールしない．
**どんだけ細かい話やねん**という感じなので無視する．

### onScale
**onScaleBeginからonScaleEndの間中ずっと呼び出される**
指が動いたかどうかは関係無い．定期的にずーっと呼び出される．
引数のScaleGestureDetectorを使用してどこを中心にどの程度ピンチイン・ピンチアウトされたかを取得できる．
倍率はgetScaleFactor，中心（焦点）はgetFocusX, getFocusYを使う．
倍率は**onScaleが前回呼ばれてから何倍になっったかの数値が入っている**
つまり2本指で2倍に拡大したあとじーっとしていると，最初に2が入っているがその後はずっと1．
拡大を始めてから終わるまでの間に結局何倍になったか，という情報が欲しい場合は自分で内部に保持するしか無いけれど，floatなので掛け算しまくると当然誤差が出るし，適当にBigDecimalとか使うのもいいけれど，そもそも設計として

- onScaleBegin：開始にちょっとキャッシュ消したりするだけ
- onScale：onScrollと同様に1回で完結するような処理にしておく
- onScaleEnd：終了時にちょっとキャッシュを消したりするだけ

としておく方が良いと思う．


# まとめ
よくある処理をまとめると下記の通り

- タップ　：　onDown → (onShowPress) → onSingleTapUpの順に呼び出される
- スクロール　：　onDown → (onShowPress) -> onScroll -> (onFling)の順に呼び出される
- ピンチイン・アウト　：　onScaleに完結する処理を書いておく