Вопрос по android – Android Два пальца вращения

27

Я пытаюсь реализовать вращение двумя пальцами в Android, но это не совсем так, как ожидалось. Цель состоит в том, чтобы реализовать вращение, как это делает Google Планета Земля (вращение изображения двумя пальцами вокруг фокуса). В настоящее время мой слушатель ротации выглядит так:

 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, поэтому у него нет ошибки сенсорного экрана.

Благодарност

У вас проблемы с углами, а не с GestureDection и обработкой. Симптомы, о которых вы сообщаете, являются именно теми, которые ожидаются при неправильном расчете разницы углов. Viktor Latypov
ScaleGestureDetector можно использовать только для масштабирования (что я уже реализовал с помощью класса), а GestureDetector можно использовать только для жестов одним касанием. Android не имеет детектора жестов поворота по умолчанию. pretobomba
Я бы посмотрел на эти классы: GestureDetector (источни) ScaleGestureDetector (источни) Они содержат несколько хороших трюков для MeTTeO
Pretomba, Я просто предложил вам взглянуть на эти реализации встроенных классов и увидеть, как можно аккуратно обрабатывать события двумя пальцами. Виктор, В Java есть оператор остатка "%", поэтому вам не нужно использовать метод и цикл while для этого ... MeTTeO
Спасибо, добавил ваше предложение в мой ответ. Viktor Latypov

Ваш Ответ

6   ответов
53
Улучшения класса:озвращено значение @angle с момента начала вращения удаление ненужных функций Упрощение получить позицию первого указателя только после того, как второй указатель вниз
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);'ImplementsRotationGestureDetector.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.

Это прекрасно работает, большое спасибо. jmc
Это хорошо работает для меня. Я добавил несколько пользовательских фильтров для своих собственных нужд, но это сэкономило мне много времени. Благодарность KnucklesTheDog
таща мой палец в одном направлении, я получаю 178.33331, -177.07356, -177.14868, 131.9633 Я имею в виду значения, которые вообще не имеют смысла. фиктивный Tyler Davis
Тайлер Дэвис, у меня была такая же проблема. Смотрите мой ответ ниже для решения. aaronmarino
Просто чтобы быть ясно, на самом деле это не обрабатывает вращение правильно? Он просто получает угол поворота объекта, так что его можно использовать как вызов Matrix.postRotate для фактического поворота представлени BigBoy1337
11

что он не работает для маленьких видов, так как при касании за пределами вида вычисление угла было неправильным. Решение состоит в том, чтобы получить исходное местоположение вместо просто 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);
}
}
Это здорово! Благодарность! Единственная проблема, которую я получаю, заключается в том, что после первого поворота каждое вращение сначала сбрасывает угол до 0. Я не мог решить эту проблему в течение некоторого времени. Любые идеи о том, как реализовать delta-anglebetweenlines? Erwin Lengkeek
Мне удалось исправить это самостоятельно, сохранив старый угол в ACTOIN_POINTER_DOWN и добавив его в mAngle в ACTION_MOVE (и используя modulo 360 для этого значения). Я надеюсь, что это может быть полезно для того, кто пытается сделать то же самое, что и я. Erwin Lengkeek
Я бы проголосовал десять раз, если бы мог. Xalamadrax
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);
}
}
Можете ли вы рассказать, как использовать этот класс в деятельности? Vins
@ vins, смотри мой ответ. leszek.hanusz
У меня на samsung s8 + это тоже работает, очень хорошо на opengl Arjun
5

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

вместо тог

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

тебе нужно написать

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

And 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

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), вы можете использовать оператор '%' вместо std :: fmod: @ в C +

float ClipAngleTo0_360( float Angle ) { return Angle % 360.0; }
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));
источник: Searchcode.com / codesearch / просмотр / 63812295 vihkat

Похожие вопросы