web-dev-qa-db-fra.com

Android deux rotation des doigts

J'essaie d'implémenter la rotation à deux doigts dans Android, mais cela ne fonctionne pas tout à fait comme prévu. L’objectif est d’implémenter la rotation comme le fait Google Earth (rotation de l’image autour du point focal avec deux doigts) Actuellement, mon auditeur de rotation ressemble à ceci:

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

Cependant, chaque fois que je tourne, l'angle de rotation est beaucoup plus grand et il tourne parfois du mauvais côté. Des idées pour résoudre le problème?

En passant, je le teste sur un Motorola Atrix, il n’a donc pas le bogue de l’écran tactile.

Merci

27
pretobomba

Améliorations de la classe:

  • l'angle retourné est total depuis le début de la rotation
  • supprimer les fonctions inutiles
  • simplification
  • obtenir la position du premier pointeur seulement après que le deuxième pointeur soit abaissé
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);
    }
}

Comment l'utiliser:

  1. Placez la classe ci-dessus dans un fichier séparé RotationGestureDetector.Java
  2. créer un champ privé mRotationDetector de type RotationGestureDetector dans votre classe d'activité et créer une nouvelle instance du détecteur lors de l'initialisation (méthode onCreate par exemple) et donner comme paramètre une classe implémentant la méthode onRotation (ici le activity = this
  3. Dans la méthode onTouchEvent, envoyez les événements tactiles reçus au détecteur de gestes avec 'mRotationDetector.onTouchEvent(event);
  4. Implémente RotationGestureDetector.OnRotationGestureListener dans votre activité et ajoutez la méthode 'public void OnRotation(RotationGestureDetector rotationDetector)' dans l'activité. Dans cette méthode, obtenez l'angle avec rotationDetector.getAngle() 

Exemple:

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

}

Remarque:

Vous pouvez également utiliser la classe RotationGestureDetector dans une View au lieu d'une Activity.

53
leszek.hanusz

Voici mon amélioration sur la réponse de Leszek. J'ai trouvé que cela ne fonctionnait pas pour les petites vues, car lorsqu'un calcul sortait de la vue, le calcul de l'angle était faux. La solution consiste à obtenir l'emplacement brut au lieu de simplement getX/Y. 

Crédit à ce fil pour obtenir les points bruts sur une vue rotative.

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

J'ai essayé une combinaison de réponses qui sont ici mais cela ne fonctionnait toujours pas parfaitement alors j'ai dû le modifier un peu. 

Ce code vous donne l'angle delta à chaque rotation, il fonctionne parfaitement pour moi, je l'utilise pour faire pivoter un objet dans 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);
}
}
8
Jorge Garcia

Il y a encore des erreurs, voici la solution qui a parfaitement fonctionné pour moi ...

au lieu de 

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

tu as besoin d'écrire

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

Et angleBetweenLines devrait être

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

Ensuite, l'angle que vous obtenez est l'angle avec lequel vous devez faire pivoter l'image ...

ImageAngle += angle...
5
Nir Hartmann

Vous avez un problème ici:

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

Vous devez couper les angles dans la plage [0..2 * Pi] et calculer soigneusement la différence angulaire dans la plage (-Pi .. + Pi).

Voici le code pour la plage d'angle 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;
}

En C++, je coderais le ClipAngleTo0_360 comme

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

où std :: fmod renvoie le reste en virgule flottante.

En Java, vous pouvez utiliser quelque chose comme

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

Oui, une arithmétique à virgule flottante prudente est bien meilleure que la boucle while () évidente.

Comme MeTTeO l'a mentionné (référence Java, 15.17.3), vous pouvez utiliser l'opérateur '%' à la place de std :: fmod de C++:

float ClipAngleTo0_360( float Angle ) { return Angle % 360.0; }
4
Viktor Latypov

J'ai essayé beaucoup d'exemples. Mais seulement ça marche bien .:

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

}

Dans votre rappel:

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