Androidで二本指の回転ジェスチャーを検出する

## 考え方

下図のように、二つのベクトルの間の角度を計算することを考えます。

![ベクトルのなす角](https://qiita-image-store.s3.amazonaws.com/0/56668/cf7e7bfc-f1d0-0bbc-cb17-4fa35a2a3806.png)

これは図から明らかなように二つのベクトルそれぞれの**x軸からの角度の差**です。
それではあるベクトルについて**x軸からの角度**を計算するにはどうすれば良いのでしょうか？
これは三角法の定義から簡単にわかります。

![角度の計算](https://qiita-image-store.s3.amazonaws.com/0/56668/cf69255d-ca8d-f521-4485-5e0ae8619eeb.png)

tan-1 という見慣れない式がでてきますが、これは tan の逆の計算をすることのできる関数です。arctan や atan と書かれることもあります。
この計算の詳細を知る必要はありません。なぜなら`Math.atan()`というメソッドを呼び出すだけで計算することができるからです。
ただし、`Math.atan()` の引数が分数になっている場合により使いやすい`Math.atan2()`というメソッドがあるので、今回はこちらを使用します。
`atan2`の詳細は[Wikipedia](http://ja.wikipedia.org/wiki/%E9%80%86%E4%B8%89%E8%A7%92%E9%96%A2%E6%95%B0#.E3.82.A2.E3.83.BC.E3.82.AF.E3.82.BF.E3.83.B3.E3.82.B8.E3.82.A7.E3.83.B3.E3.83.88.E3.81.AE_2_.E5.BC.95.E6.95.B0.E3.81.AE.E5.A4.89.E7.A8.AE)で。

これで、あるベクトルについての**x軸からの角度**は計算できました。
同じ計算をもう一つのベクトルでも行い、出てきた値の差を取ればそれがベクトル同士のなす角度になります。

ジェスチャーの実装をするときは、2本の指の片方からもう一方へ伸びるベクトルを考えます。これは**2本の指それぞれのx,y座標の差**を取ることで計算できます。
ベクトルが計算できたら、このベクトルがx軸となす角度を計算します(atan2を使う)。
そして、**直前の角度の計算結果と現在の計算結果の差**を取ります。
この差がジェスチャーによる回転角です。

## 実装

GestureDetector(Compat) や ScaleGestureDetector と同じように使える RotateGestureDetector を作ってみました。
実装にあたっては最初のイベントの時だけ直前の角度が存在しないので、その分の場合分けが必要になります。あとは上で説明したような考え方をそのままあてはめます。

上に出てきていない `focus` という変数がありますが、これは2本の指の中心点を表します。画面を描画する場合、この中心点を回転の中心とすると上手くいくことが多いと思います。

```java
import android.content.Context;
import android.view.MotionEvent;
 
public class RotateGestureDetector {
  public static interface OnRotateListener {
    boolean onRotate(float degrees, float focusX, float focusY);
  }
 
  public static class SimpleOnRotateGestureDetector implements OnRotateListener {
    @Override
    public boolean onRotate(float degrees, float focusX, float focusY) {
      return false;
    }
  }
 
  private static float RADIAN_TO_DEGREES = (float) (180.0 / Math.PI);
  private OnRotateListener listener;
  private float prevX = 0.0f;
  private float prevY = 0.0f;
  private float prevTan;
 
  public RotateGestureDetector(OnRotateListener listener) {
    this.listener = listener;
  }
 
  public boolean onTouchEvent(MotionEvent event) {
    if (event.getPointerCount() == 2 && event.getActionMasked() == MotionEvent.ACTION_MOVE) {
      boolean result = true;
      float x = event.getX(1) - event.getX(0);
      float y = event.getY(1) - event.getY(0);
      float focusX = (event.getX(1) + event.getX(0)) * 0.5f;
      float focusY = (event.getY(1) + event.getY(0)) * 0.5f;
      float tan = (float) Math.atan2(y, x);
 
      if (prevX != 0.0f && prevY != 0.0f) {
        result = listener.onRotate((tan - prevTan) * RADIAN_TO_DEGREES, focusX, focusY);
      }
 
      prevX = x;
      prevY = y;
      prevTan = tan;
      return result;
    } else {
      prevX = prevY = prevTan = 0.0f;
      return true;
    }
  }
}
```

## 使い方

```java
public class UserActivity extends Activity {
  private RotateGestureDetector rotateGestureDetector;
  
  protected void onCreate(Bundle savedInstanceState) {
    rotateGestureDetector = new RotateGestureDetector(new RotateGestureDetector.SimpleOnRotateGestureDetector() {
      @Override
      public boolean onRotate(float degrees, float focusX, float focusY) {
        // something with rotate
        return true;
      }
    });
  }
 
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    rotateGestureDetector.onTouchEvent(event);
    return true;
  }
}
```