Android Two finger rotation
android, gesture-recognition, rotation
Solution
Improvements of the class:
- angle returned is total since rotation has begun
- removing unnecessary functions
- simplification
- get position of first pointer only after second pointer is down
public class RotationGestureDetector {
private static final int INVALID_POINTER_ID = -1;
private float fX, fY, sX, sY;
private int ptrID1, ptrID2;
private float mAngle;
private OnRotationGestureListener mListener;
public float getAngle() {
return mAngle;
}
public RotationGestureDetector(OnRotationGestureListener listener){
mListener = listener;
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
}
public boolean onTouchEvent(MotionEvent event){
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ptrID1 = event.getPointerId(event.getActionIndex());
break;
case MotionEvent.ACTION_POINTER_DOWN:
ptrID2 = event.getPointerId(event.getActionIndex());
sX = event.getX(event.findPointerIndex(ptrID1));
sY = event.getY(event.findPointerIndex(ptrID1));
fX = event.getX(event.findPointerIndex(ptrID2));
fY = event.getY(event.findPointerIndex(ptrID2));
break;
case MotionEvent.ACTION_MOVE:
if(ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID){
float nfX, nfY, nsX, nsY;
nsX = event.getX(event.findPointerIndex(ptrID1));
nsY = event.getY(event.findPointerIndex(ptrID1));
nfX = event.getX(event.findPointerIndex(ptrID2));
nfY = event.getY(event.findPointerIndex(ptrID2));
mAngle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
if (mListener != null) {
mListener.OnRotation(this);
}
}
break;
case MotionEvent.ACTION_UP:
ptrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
ptrID2 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_CANCEL:
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
break;
}
return true;
}
private float angleBetweenLines (float fX, float fY, float sX, float sY, float nfX, float nfY, float nsX, float nsY)
{
float angle1 = (float) Math.atan2( (fY - sY), (fX - sX) );
float angle2 = (float) Math.atan2( (nfY - nsY), (nfX - nsX) );
float angle = ((float)Math.toDegrees(angle1 - angle2)) % 360;
if (angle < -180.f) angle += 360.0f;
if (angle > 180.f) angle -= 360.0f;
return angle;
}
public static interface OnRotationGestureListener {
public void OnRotation(RotationGestureDetector rotationDetector);
}
}
How to use it:
- Put the above class in a separate file `RotationGestureDetector.java`
- create a private field `mRotationDetector` of type `RotationGestureDetector` in your activity class and create a new instance of the detector during the initialization (`onCreate` method for example) and give as parameter a class implementing the `onRotation` method (here the `activity = this`).
- In the method `onTouchEvent`, send the touch events received to the gesture detector with '`mRotationDetector.onTouchEvent(event);`'
- Implements `RotationGestureDetector.OnRotationGestureListener` in your activity and add the method '`public void OnRotation(RotationGestureDetector rotationDetector)`' in the activity. In this method, get the angle with `rotationDetector.getAngle()`
Example:
public class MyActivity extends Activity implements RotationGestureDetector.OnRotationGestureListener {
private RotationGestureDetector mRotationDetector;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRotationDetector = new RotationGestureDetector(this);
}
@Override
public boolean onTouchEvent(MotionEvent event){
mRotationDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
@Override
public void OnRotation(RotationGestureDetector rotationDetector) {
float angle = rotationDetector.getAngle();
Log.d("RotationGestureDetector", "Rotation: " + Float.toString(angle));
}
}
Note:
You can also use the `RotationGestureDetector` class in a `View` instead of an `Activity`.
Problem
I am trying to implement two finger rotation in android however, it is not quite working as expected. The goal is to implement rotation like Google Earth does (two-finger rotating the image around the focal point). Currently my rotation listener looks like this: ``` private class RotationGestureListener { private static final int INVALID_POINTER_ID = -1; private float fX, fY, sX, sY, focalX, focalY; private int ptrID1, ptrID2; public RotationGestureListener(){ ptrID1 = INVALID_POINTER_ID; ptrID2 = INVALID_POINTER_ID; } public boolean onTouchEvent(MotionEvent event){ switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: sX = event.getX(); sY = event.getY(); ptrID1 = event.getPointerId(0); break; case MotionEvent.ACTION_POINTER_DOWN: fX = event.getX(); fY = event.getY(); focalX = getMidpoint(fX, sX); focalY = getMidpoint(fY, sY); ptrID2 = event.getPointerId(event.getActionIndex()); break; case MotionEvent.ACTION_MOVE: if(ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID){ float nfX, nfY, nsX, nsY; nfX = event.getX(event.findPointerIndex(ptrID1)); nfY = event.getY(event.findPointerIndex(ptrID1)); nsX = event.getX(event.findPointerIndex(ptrID2)); nsY = event.getY(event.findPointerIndex(ptrID2)); float angle = angleBtwLines(fX, fY, nfX, nfY, sX, sY, nsX, nsY); rotateImage(angle, focalX, focalY); fX = nfX; fY = nfY; sX = nfX; sY = nfY; } break; case MotionEvent.ACTION_UP: ptrID1 = INVALID_POINTER_ID; break; case MotionEvent.ACTION_POINTER_UP: ptrID2 = INVALID_POINTER_ID; break; } return false; } private float getMidpoint(float a, float b){ return (a + b) / 2; } private float angleBtwLines (float fx1, float fy1, float fx2, float fy2, float sx1, float sy1, float sx2, float sy2){ float angle1 = (float) Math.atan2(fy1 - fy2, fx1 - fx2); float angle2 = (float) Math.atan2(sy1 - sy2, sx1 - sx2); return (float) Math.toDegrees((angle1-angle2)); } } ``` However whenever I rotate the angle of rotation is much larger and it sometimes it rotates to the wrong side. Any ideas on how to fix this? By the way I am testing it on a Motorola Atrix, so it does not have the touchscreen bug. Thanks