Подтвердить что ты не робот

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

Однако всякий раз, когда я поворачиваю, угол поворота намного больше, и иногда он вращается с неправильной стороны. Любые идеи о том, как исправить это?

Кстати, я тестирую его на Motorola Atrix, поэтому у него нет ошибки сенсорного экрана.

Спасибо

4b9b3361

Ответ 1

Усовершенствования класса:

  • Возвращаемый угол
  • является полным, так как началось вращение
  • удаление ненужных функций
  • упрощение
  • получить позицию первого указателя только после того, как второй указатель опущен.
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 отправьте события касания, полученные в детектор жестов, с помощью <<28 >
  • Реализует 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.

Ответ 2

Здесь мое улучшение по Лешеку. Я обнаружил, что он не работал для небольших просмотров, так как когда прикосновение выходило за пределы угла обзора, неправильный расчет угла. Решение состоит в том, чтобы получить исходное местоположение, а не просто 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);
}
}

Ответ 3

Я попробовал комбинацию ответов, которые здесь, но он все еще не работает отлично, поэтому мне пришлось немного изменить его.

Этот код дает вам угол дельта на каждом повороте, он отлично работает для меня, я использую его для поворота объекта в 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);
}
}

Ответ 4

Есть еще некоторые ошибки, вот решение, которое отлично сработало для меня...

вместо

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...

Ответ 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));
}

Вы должны закрепить углы в диапазоне [0..2 * Pi] и тщательно рассчитать разность angular в диапазоне (-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;
}

В С++ я бы закодировал 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), вы можете использовать оператор "%" вместо С++ std:: fmod:

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

Ответ 6

Я попробовал много примеров. Но только эта работа хорошая.

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