27 votes

Comment trouver la position absolue du clic lors d'un zoom avant ?

Veuillez consulter chaque section ci-dessous pour une description de mon problème décrit de trois manières différentes. J'espère que cela aidera les gens à répondre.

Problème : Comment trouver une paire de coordonnées exprimées dans le canevas/espace utilisateur lorsque vous ne disposez que des coordonnées exprimées en termes d'image zoomée, compte tenu du point d'échelle et du facteur d'échelle d'origine ?

Problème en pratique :

J'essaie actuellement de reproduire la fonctionnalité de zoom utilisée dans des applications telles que la galerie / les cartes, lorsque vous pouvez pincer pour zoomer/dézoomer avec le zoom se déplaçant vers le point central du pincement.

En bas, je sauvegarde le point central du zoom (qui est en coordonnées X,Y basées sur l'écran actuel). Je fais ensuite en sorte que cette fonction agisse lorsqu'un geste de "mise à l'échelle" est détecté :

class ImageScaleGestureDetector extends SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if(mScaleAllowed) 
            mCustomImageView.scale(detector.getScaleFactor(), mCenterX, mCenterY);
        return true;
    }
}   

La fonction d'échelle du CustomImageView ressemble à ceci :

public boolean scale(float scaleFactor, float focusX, float focusY) {
    mScaleFactor *= scaleFactor;

    // Don't let the object get too small or too large.
    mScaleFactor = Math.max(MINIMUM_SCALE_VALUE, Math.min(mScaleFactor, 5.0f));

    mCenterScaleX = focusX;
    mCenterScaleY = focusY;

    invalidate();

    return true;
}

Le dessin de l'image mise à l'échelle est réalisé en surchargeant la méthode onDraw qui met à l'échelle le canevas autour du centre et y dessine l'image.

@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();
    canvas.translate(mCenterScaleX, mCenterScaleY);
    canvas.scale(mScaleFactor, mScaleFactor);
    canvas.translate(-mCenterScaleX, -mCenterScaleY);
    mIcon.draw(canvas);
    canvas.restore();
}

Tout fonctionne bien lorsque l'on met à l'échelle à partir du facteur d'échelle 1, car les coordonnées initiales mCenterX et mCenterY sont basées sur l'écran du périphérique. 10, 10 sur l'appareil correspond à 10, 10 sur le canevas.

Cependant, si vous avez déjà effectué un zoom, la prochaine fois que vous cliquerez sur la position 10, 10, elle ne correspondra plus à 10, 10 dans le canevas en raison de la mise à l'échelle et de la transformation qui ont déjà été effectuées.

Problème d'abstraction :

L'image ci-dessous est un exemple d'une opération de zoom autour du point central A. Chaque case représente la position et la taille de la vue avec ce facteur d'échelle (1, 2, 3, 4, 5).

Example

Dans l'exemple, si vous modifiez l'échelle d'un facteur 2 autour de A et que vous cliquez sur la position B, les valeurs X et Y indiquées pour B seront basées sur la position de l'écran et non sur la position relative à 0,0 du canevas initial.

Je dois trouver un moyen d'obtenir la position absolue de B.

11voto

Graeme Points 9167

Ainsi, après avoir redessiné le problème, j'ai trouvé la solution que je cherchais. Elle est passée par quelques itérations mais voici comment je l'ai trouvée :

Solution Description

B - Point, centre de l'opération de l'échelle

A1, A2, A3 - Points, égaux dans l'espace utilisateur mais différents dans l'espace toile.

Vous connaissez les valeurs de Bx et By car ils sont toujours constants, quel que soit le facteur d'échelle (vous connaissez cette valeur à la fois dans l'espace toile et dans l'espace utilisateur).

Vous savez Ax & Ay dans l'espace utilisateur afin que vous puissiez trouver la distance entre Ax à Bx et Ay à By . Cette mesure est en espace utilisateur, pour la convertir en espace toile, il suffit de la diviser par le facteur d'échelle. (Une fois converties en espace toile, vous pouvez voir ces lignes en rouge, orange et jaune).

Comme point B est constante, la distance entre lui et les bords est constante (ceux-ci sont représentés par des lignes bleues). Cette valeur est égale dans l'espace utilisateur et dans l'espace toile.

Vous connaissez la largeur du canevas en espace canevas, donc en soustrayant ces deux mesures d'espace canevas ( Ax à Bx et Bx à Edge ) de la largeur totale, il vous reste les coordonnées du point A dans l'espace du canevas :

public float[] getAbsolutePosition(float Ax, float Ay) {

    float fromAxToBxInCanvasSpace = (mCenterScaleX - Ax) / mScaleFactor;
    float fromBxToCanvasEdge = mCanvasWidth - Bx;
    float x = mCanvasWidth - fromAxToBxInCanvasSpace - fromBxToCanvasEdge;

    float fromAyToByInCanvasSpace = (mCenterScaleY - Ay) / mScaleFactor;
    float fromByToCanvasEdge = mCanvasHeight - By;
    float y = mCanvasHeight - fromAyToByInCanvasSpace - fromByToCanvasEdge;

    return new float[] { x, y };
}

Le code et l'image ci-dessus décrivent le moment où vous cliquez en haut à gauche du centre d'origine. J'ai utilisé la même logique pour trouver A, quel que soit le quadrant dans lequel il se trouve, et je l'ai remanié comme suit :

public float[] getAbsolutePosition(float Ax, float Ay) {

    float x = getAbsolutePosition(mBx, Ax);
    float y = getAbsolutePosition(mBy, Ay); 

    return new float[] { x, y };
}

private float getAbsolutePosition(float oldCenter, float newCenter, float mScaleFactor) {
    if(newCenter > oldCenter) {
        return oldCenter + ((newCenter - oldCenter) / mScaleFactor);
    } else {
        return oldCenter - ((oldCenter - newCenter) / mScaleFactor);
    }
}

2voto

Keanu Pang Points 21

Voici ma solution basée sur la réponse de Graeme :

public float[] getAbsolutePosition(float Ax, float Ay) {
    MatrixContext.drawMatrix.getValues(mMatrixValues);

    float x = mWidth - ((mMatrixValues[Matrix.MTRANS_X] - Ax) / mMatrixValues[Matrix.MSCALE_X])
            - (mWidth - getTranslationX());

    float y = mHeight - ((mMatrixValues[Matrix.MTRANS_Y] - Ay) / mMatrixValues[Matrix.MSCALE_X])
            - (mHeight - getTranslationY());

    return new float[] { x, y };
}

les paramètres Ax et Ay sont les points que l'utilisateur touche via onTouch(), j'ai possédé mon instance de matrice statique dans la classe MatrixContext pour contenir les valeurs précédentes mises à l'échelle/traduites.

1voto

Martyn Points 5662

Vraiment désolé, c'est une réponse brève, je suis pressé. Mais je me suis aussi penché sur la question récemment. http://code.google.com/p/Android-multitouch-controller/ pour faire ce que vous voulez (je pense - j'ai dû parcourir votre post). J'espère que cela vous aidera. Si cela ne suffit pas, j'y jetterai un coup d'œil ce soir et je verrai si je peux vous aider davantage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X