Android两指旋转

pre*_*mba 28 android rotation gesture-recognition

我试图在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));
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,每当我旋转时,旋转角度要大得多,有时它会旋转到错误的一侧.有想法该怎么解决这个吗?

顺便说一句,我在摩托罗拉Atrix上测试它,所以它没有触摸屏错误.

谢谢

les*_*usz 60

课程改进:

  • 自旋转开始以来返回的角度是总计
  • 删除不必要的功能
  • 简单化
  • 仅在第二个指针停止后才获取第一个指针的位置
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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如何使用它:

  1. 将上面的类放在一个单独的文件中 RotationGestureDetector.java
  2. 在您的活动类中创建一个mRotationDetector类型的私有字段,RotationGestureDetector并在初始化期间创建一个新的检测器实例(onCreate例如方法),并为实现该onRotation方法的类(此处为activity = this)提供参数.
  3. 在该方法中onTouchEvent,将接收到的触摸事件发送到手势检测器' 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));
    }

}
Run Code Online (Sandbox Code Playgroud)

注意:

您也可以使用RotationGestureDetector该类View而不是Activity.

  • 这对我来说效果很好。我根据自己的特定需求添加了一些自定义过滤,但这节省了我很多时间。谢谢。 (2认同)
  • 泰勒·戴维斯,我也遇到了同样的问题。请参阅下面我的回答以获取解决方案。 (2认同)
  • 只是为了清楚,这实际上并没有处理旋转对吗?它只是获取对象已被旋转的角度,以便它可以像Matrix.postRotate调用一样实际旋转视图 (2认同)
  • 值得注意的是,angleBetweenLines() 提供了累积角度。如果需要增量角度,则应在 onTouchEvent() 内写入 mDeltaAngle = mAngle - mPreviousAngle 并返回 mDeltaAngle。还要在“if (mListener != null)”块内写入 mPreviousAngle = mAngle。不要忘记将其初始化为零,并且在开关块中的其他“情况”中也将其设置为零。 (2认同)

aar*_*ino 14

这是我对Leszek答案的改进.我发现他不适用于小视图,因为当触摸超出视角时,角度计算是错误的.解决方案是获取原始位置而不仅仅是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);
}
}
Run Code Online (Sandbox Code Playgroud)

  • 我设法通过在 ACTOIN_POINTER_DOWN 中保存旧角度并将其添加到 ACTION_MOVE 中的 mAngle(并对该值使用模 360)来自己修复它。我希望这对尝试和我一样的人有用。 (3认同)

Jor*_*cia 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);
}
}
Run Code Online (Sandbox Code Playgroud)


Vik*_*pov 5

你在这里有问题:

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));
}
Run Code Online (Sandbox Code Playgroud)

您必须将角度剪裁到 [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;
}
Run Code Online (Sandbox Code Playgroud)

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

float ClipAngleTo0_360( float Angle ) { return std::fmod( Angle, 360.0f ); }
Run Code Online (Sandbox Code Playgroud)

其中 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;
}
Run Code Online (Sandbox Code Playgroud)

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

正如 MeTTeO 提到的(java 参考,15.17.3),您可以使用 '%' 运算符代替 C++ 的 std::fmod:

float ClipAngleTo0_360( float Angle ) { return Angle % 360.0; }
Run Code Online (Sandbox Code Playgroud)


Nir*_*ann 5

还有一些错误,这里的解决方案对我来说非常合适......

代替

float angle = angleBtwLines(fX, fY, nfX, nfY, sX, sY, nsX, nsY);
Run Code Online (Sandbox Code Playgroud)

你需要写

float angle = angleBtwLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
Run Code Online (Sandbox Code Playgroud)

和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));
}
Run Code Online (Sandbox Code Playgroud)

然后你得到的角度是你应该旋转图像的角度......

ImageAngle += angle...
Run Code Online (Sandbox Code Playgroud)