13
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-11-18

考え方

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

ベクトルのなす角

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

角度の計算

tan-1 という見慣れない式がでてきますが、これは tan の逆の計算をすることのできる関数です。arctan や atan と書かれることもあります。
この計算の詳細を知る必要はありません。なぜならMath.atan()というメソッドを呼び出すだけで計算することができるからです。
ただし、Math.atan() の引数が分数になっている場合により使いやすいMath.atan2()というメソッドがあるので、今回はこちらを使用します。
atan2の詳細はWikipediaで。

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

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

実装

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

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

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;
    }
  }
}

使い方

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;
  }
}
13
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?