studio programacion herramientas fundamentos con avanzado aplicaciones android rotation gesture-recognition

programacion - Rotación de dos dedos de Android



manual de android en pdf (6)

Mejoras de la clase:

  • El ángulo devuelto es total ya que la rotación ha comenzado.
  • eliminando funciones innecesarias
  • simplificación
  • obtener la posición del primer puntero solo después de que el segundo puntero esté abajo

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

Cómo usarlo:

  1. Coloque la clase anterior en un archivo separado RotationGestureDetector.java
  2. cree un campo privado mRotationDetector de tipo RotationGestureDetector en su clase de actividad y cree una nueva instancia del detector durante la inicialización (método onCreate por ejemplo) y proporcione como parámetro una clase que implemente el método onRotation (aquí la activity = this ).
  3. En el método onTouchEvent , envíe los eventos táctiles recibidos al detector de gestos con '' mRotationDetector.onTouchEvent(event); ''
  4. Implementa RotationGestureDetector.OnRotationGestureListener en tu actividad y agrega el método '' public void OnRotation(RotationGestureDetector rotationDetector) '' en la actividad. En este método, obtenga el ángulo con rotationDetector.getAngle()

Ejemplo:

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

Nota:

También puede utilizar la clase RotationGestureDetector en una View lugar de una Activity .

Estoy tratando de implementar la rotación de dos dedos en Android, sin embargo, no está funcionando como se esperaba. El objetivo es implementar la rotación como lo hace Google Earth (con dos dedos girando la imagen alrededor del punto focal). Actualmente mi oyente de rotación se ve así:

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

Sin embargo, cada vez que giro el ángulo de rotación es mucho mayor y, a veces, gira hacia el lado equivocado. ¿Alguna idea sobre cómo solucionar este problema?

Por cierto, lo estoy probando en un Motorola Atrix, por lo que no tiene el error de la pantalla táctil.

Gracias


Aquí está mi mejora en la respuesta de Leszek. Descubrí que no funcionaba para vistas pequeñas, ya que cuando un toque salía de la vista, el cálculo del ángulo era incorrecto. La solución es obtener la ubicación en bruto en lugar de solo obtenerX / Y.

Gracias a este hilo por obtener los puntos en bruto en una vista giratoria.

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


He intentado muchos ejemplos. Pero solo esta bien de trabajo .:

public class RotationGestureDetector { public interface RotationListener { public void onRotate(float deltaAngle); } protected float mRotation; private RotationListener mListener; public RotationGestureDetector(RotationListener listener) { mListener = listener; } private float rotation(MotionEvent event) { double delta_x = (event.getX(0) - event.getX(1)); double delta_y = (event.getY(0) - event.getY(1)); double radians = Math.atan2(delta_y, delta_x); return (float) Math.toDegrees(radians); } public void onTouch(MotionEvent e) { if (e.getPointerCount() != 2) return; if (e.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { mRotation = rotation(e); } float rotation = rotation(e); float delta = rotation - mRotation; mRotation += delta; mListener.onRotate(delta); } }

En su devolución de llamada:

view.setRotation(view.getRotetion() -deltaAndle));


Intenté una combinación de respuestas que están aquí, pero aún así no funcionó perfectamente, así que tuve que modificarlo un poco.

Este código le da el ángulo delta en cada rotación, me funciona perfectamente, lo estoy usando para rotar un objeto en 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); } }


Tienes un problema aquí:

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

Debe recortar los ángulos al rango [0..2 * Pi] y luego calcular con cuidado la diferencia angular en el rango (-Pi .. + Pi).

Aquí está el código para el rango de ángulo de 0..360

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

En C ++ codificaría el ClipAngleTo0_360 como

float ClipAngleTo0_360( float Angle ) { return std::fmod( Angle, 360.0f ); }

donde std :: fmod devuelve el resto de punto flotante.

En java puedes usar algo como

float ClipAngleTo0_360( float Angle ) { float Res = Angle; while(Angle < 0) { Angle += 360.0; } while(Angle >= 360.0) { Angle -= 360.0; } return Res; }

Sí, el cuidado de la aritmética de punto flotante es mucho mejor que el obvio bucle while ().

Como mencionó MeTTeO (referencia de java, 15.17.3), puede usar el operador ''%'' en lugar de std :: fmod de C ++:

float ClipAngleTo0_360( float Angle ) { return Angle % 360.0; }


Todavía hay algunos errores, aquí está la solución que funcionó perfecto para mí ...

en lugar de

float angle = angleBtwLines(fX, fY, nfX, nfY, sX, sY, nsX, nsY);

necesitas escribir

float angle = angleBtwLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);

Y angleBetweenLines debe ser

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

Entonces el ángulo que obtienes es el ángulo en el que debes rotar la imagen ...

ImageAngle += angle...