Android Two finger rotation Android Two finger rotation android android

Android Two finger rotation


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:

  1. Put the above class in a separate file RotationGestureDetector.java
  2. 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).
  3. In the method onTouchEvent, send the touch events received to the gesture detector with 'mRotationDetector.onTouchEvent(event);'
  4. 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.


Here's my improvement on Leszek's answer. I found that his didn't work for small views as when a touch went outside the view the angle calculation was wrong. The solution is to get the raw location instead of just getX/Y.

Credit to this thread for getting the raw points on a rotatable view.

public class RotationGestureDetector {    private static final int INVALID_POINTER_ID = -1;    private PointF mFPoint = new PointF();    private PointF mSPoint = new PointF();    private int mPtrID1, mPtrID2;    private float mAngle;    private View mView;    private OnRotationGestureListener mListener;    public float getAngle() {        return mAngle;    }    public RotationGestureDetector(OnRotationGestureListener listener, View v) {        mListener = listener;        mView = v;        mPtrID1 = INVALID_POINTER_ID;        mPtrID2 = INVALID_POINTER_ID;    }    public boolean onTouchEvent(MotionEvent event) {        switch (event.getActionMasked()) {            case MotionEvent.ACTION_OUTSIDE:                Log.d(this, "ACTION_OUTSIDE");                break;            case MotionEvent.ACTION_DOWN:                Log.v(this, "ACTION_DOWN");                mPtrID1 = event.getPointerId(event.getActionIndex());                break;            case MotionEvent.ACTION_POINTER_DOWN:                Log.v(this, "ACTION_POINTER_DOWN");                mPtrID2 = event.getPointerId(event.getActionIndex());                getRawPoint(event, mPtrID1, mSPoint);                getRawPoint(event, mPtrID2, mFPoint);                break;            case MotionEvent.ACTION_MOVE:                if (mPtrID1 != INVALID_POINTER_ID && mPtrID2 != INVALID_POINTER_ID) {                    PointF nfPoint = new PointF();                    PointF nsPoint = new PointF();                    getRawPoint(event, mPtrID1, nsPoint);                    getRawPoint(event, mPtrID2, nfPoint);                    mAngle = angleBetweenLines(mFPoint, mSPoint, nfPoint, nsPoint);                    if (mListener != null) {                        mListener.onRotation(this);                    }                }                break;            case MotionEvent.ACTION_UP:                mPtrID1 = INVALID_POINTER_ID;                break;            case MotionEvent.ACTION_POINTER_UP:                mPtrID2 = INVALID_POINTER_ID;                break;            case MotionEvent.ACTION_CANCEL:                mPtrID1 = INVALID_POINTER_ID;                mPtrID2 = INVALID_POINTER_ID;                break;            default:                break;        }        return true;    }    void getRawPoint(MotionEvent ev, int index, PointF point) {        final int[] location = { 0, 0 };        mView.getLocationOnScreen(location);        float x = ev.getX(index);        float y = ev.getY(index);        double angle = Math.toDegrees(Math.atan2(y, x));        angle += mView.getRotation();        final float length = PointF.length(x, y);        x = (float) (length * Math.cos(Math.toRadians(angle))) + location[0];        y = (float) (length * Math.sin(Math.toRadians(angle))) + location[1];        point.set(x, y);    }    private float angleBetweenLines(PointF fPoint, PointF sPoint, PointF nFpoint, PointF nSpoint) {        float angle1 = (float) Math.atan2((fPoint.y - sPoint.y), (fPoint.x - sPoint.x));        float angle2 = (float) Math.atan2((nFpoint.y - nSpoint.y), (nFpoint.x - nSpoint.x));        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 interface OnRotationGestureListener {        void onRotation(RotationGestureDetector rotationDetector);    }}


I tried a combination of answers that are here but it still didn't work perfectly so I had to modify it a little bit.

This code gives you the delta angle on each rotation, it works perfectly to me, I'm using it to rotate an object in OpenGL.

public class RotationGestureDetector {private static final int INVALID_POINTER_ID = -1;private float fX, fY, sX, sY, focalX, focalY;private int ptrID1, ptrID2;private float mAngle;private boolean firstTouch;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:            sX = event.getX();            sY = event.getY();            ptrID1 = event.getPointerId(0);            mAngle = 0;            firstTouch = true;            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());            mAngle = 0;            firstTouch = true;            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));                if (firstTouch) {                    mAngle = 0;                    firstTouch = false;                } else {                    mAngle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);                }                if (mListener != null) {                    mListener.OnRotation(this);                }                fX = nfX;                fY = nfY;                sX = nsX;                sY = nsY;            }            break;        case MotionEvent.ACTION_UP:            ptrID1 = INVALID_POINTER_ID;            break;        case MotionEvent.ACTION_POINTER_UP:            ptrID2 = INVALID_POINTER_ID;            break;    }    return true;}private float getMidpoint(float a, float b){    return (a + b) / 2;}float findAngleDelta( float angle1, float angle2 ){    float From = ClipAngleTo0_360( angle2 );    float To   = ClipAngleTo0_360( angle1 );    float Dist  = To - From;    if ( Dist < -180.0f )    {        Dist += 360.0f;    }    else if ( Dist > 180.0f )    {        Dist -= 360.0f;    }    return Dist;}float ClipAngleTo0_360( float Angle ) {     return Angle % 360.0f; }private float angleBetweenLines (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 findAngleDelta((float)Math.toDegrees(angle1),(float)Math.toDegrees(angle2));}public static interface OnRotationGestureListener {    public boolean OnRotation(RotationGestureDetector rotationDetector);}}