我试图在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));
}
}
然而,每当我旋转时,旋转角度会大得多,有时它会旋转到错误的一侧。关于如何解决这个问题的任何想法?
顺便说一句,我在摩托罗拉Atrix上测试它,所以它没有触摸屏错误。
由于
答案 0 :(得分:54)
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);
}
}
RotationGestureDetector.java
mRotationDetector
的私有字段RotationGestureDetector
,并在初始化期间创建一个新的检测器实例(例如onCreate
方法)并为参数提供一个类实施onRotation
方法(此处为activity = this
)。 onTouchEvent
中,使用“mRotationDetector.onTouchEvent(event);
”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));
}
}
您还可以在RotationGestureDetector
而不是View
中使用Activity
课程。
答案 1 :(得分:11)
这是我对Leszek答案的改进。我发现他不适用于小视图,因为当触摸超出视角时,角度计算是错误的。解决方案是获取原始位置而不仅仅是getX / Y.
归功于this thread以获取可旋转视图上的原始点。
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);
}
}
答案 2 :(得分:8)
我尝试了这里的答案组合,但它仍然没有完美地工作,所以我不得不稍微修改它。
此代码为您提供每次旋转的增量角度,它对我来说非常有效,我用它来旋转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);
}
}
答案 3 :(得分:5)
还有一些错误,这里的解决方案对我来说非常合适......
而不是
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));
}
然后你得到的角度是你应该按图像旋转图像的角度。
ImageAngle += angle...
答案 4 :(得分:4)
你有问题:
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));
}
您必须将角度剪切到[0..2 * Pi]范围,然后仔细计算(-Pi .. + Pi)范围内的角度差异。
这是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;
}
在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; }
答案 5 :(得分: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);
}
}
在你的回调中:
view.setRotation(view.getRotetion() -deltaAndle));