Android两个手指旋转

我试图在android中实现两个手指旋转,但它并不像预期的那样工作。 目标是实现像Google Earth一样的旋转(两指旋转焦点周围的图像)。 目前我的旋转监听器看起来像这样:

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

然而,每当我旋转angular度的旋转更大,有时它旋转到错误的一面。 有想法该怎么解决这个吗?

顺便说一句,我在摩托罗拉Atrixtesting它,所以它没有触摸屏的错误。

谢谢

Related of "Android两个手指旋转"

课堂改进:

  • 自从旋转开始以来,返回的angular度是总的
  • 去除不必要的function
  • 简单化
  • 仅在第二个指针closures后才能获取第一个指针的位置
 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); } } 

如何使用它:

  1. 把上面的类放在一个单独的文件RotationGestureDetector.java
  2. 在你的Activity类中创build一个types为RotationGestureDetector的私有字段mRotationDetector ,并在初始化过程中创build一个新的实例(例如onCreate方法),并给出一个实现onRotation方法(这里是activity = this )的类的参数。
  3. onTouchEvent方法中,通过' mRotationDetector.onTouchEvent(event);将接收到的触摸事件发送给手势检测器mRotationDetector.onTouchEvent(event);
  4. 在您的活动中实现RotationGestureDetector.OnRotationGestureListener ,并在活动中添加方法' public void OnRotation(RotationGestureDetector rotationDetector) '。 在这个方法中,用rotationDetector.getAngle()

例:

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

注意:

您也可以在View使用RotationGestureDetector类,而不是在Activity

这是我对Leszek答案的改进。 我发现他的小视angular并不适用,因为当触摸到视野外时,angular度计算是错误的。 解决的办法是得到原始位置,而不是只是getX / Y。

感谢这个线程获取旋转视图上的原始点。

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

我尝试了一些在这里的答案,但它仍然没有完美的工作,所以我不得不修改它一点点。

这个代码给你在每个旋转的angular度,这对我来说是完美的,我用它来旋转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); } } 

还有一些错误,这里是对我来说完美的解决scheme…

代替

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

你需要写

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

和angleBetweenLines应该是

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

那么你得到的angular度是你应该旋转图像的angular度…

 ImageAngle += angle... 

你在这里有一个问题:

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

您必须将angular度剪切到[0..2 * Pi]范围,并仔细计算(-Pi .. + Pi)范围内的angular度差。

这里是0..360angular度范围的代码

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

在C ++中,我会将ClipAngleTo0_360编码为

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

std :: fmod返回浮点数。

在Java中,你可以使用类似的东西

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

是的,仔细的浮点运算比明显的while()循环好得多。

正如MeTTeO提到的(java参考,15.17.3),你可以使用'%'而不是C ++的std :: fmod:

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

我尝试了很多例子。 但只有这个工作很好:

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

在你的callback中:

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