web-dev-qa-db-ja.com

ズームイン中にクリックの絶対位置を見つける方法

3つの別々の方法で説明されている私の問題の説明については、以下の各セクションを参照してください。うまくいけば、人々が答えるのを助けるべきです。

問題:元のスケールポイントとスケール係数が与えられた場合、ズームされた画像でのみ座標が表現されている場合、キャンバス/ユーザースペースで表現された座標のペアをどのように見つけますか?

実際の問​​題:

私は現在、ギャラリー/マップなどのアプリで使用されているズーム機能を複製しようとしています。ピンチしてズーム/ズームアウトすると、ズームがピンチの中点に向かって移動します。

下に移動すると、ズームの中心点(現在の画面に基づくX、Y座標)を保存します。次に、「スケール」ジェスチャが検出されたときにこの関数を動作させます。

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

CustomImageViewのスケール関数は次のようになります。

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

拡大縮小された画像の描画は、キャンバスを中央に拡大縮小し、それに画像を描画するonDrawメソッドをオーバーライドすることによって実現されます。

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

これは、ScaleFactor 1からスケーリングするときにすべて正常に機能します。これは、最初のmCenterXとmCenterYがデバイス画面に基づく座標であるためです。デバイスの10、10は、キャンバスの10、10です。

ただし、すでにズームした後、次に位置10、10をクリックすると、スケーリングと変換がすでに実行されているため、キャンバスの10、10に対応しなくなります。

抽象化の問題:

以下の画像は、中心点A周辺のズーム操作の例です。各ボックスは、その倍率(1、2、3、4、5)でのビューの位置とサイズを表しています。

Example

この例では、Aの周りを2倍に拡大縮小した後、位置Bをクリックすると、Bとして報告されるX、Yは、最初のキャンバスの0,0を基準とした位置ではなく、画面の位置に基づきます。

Bの絶対位置を取得する方法を見つける必要があります。

29
Graeme

それで、問題を再描画した後、私は探していた解決策を見つけました。それは数回の反復を経ましたが、これが私がそれをどのように解決したかです:

Solution Description

B-ポイント、スケール操作の中心

A1、A2、A3-ポイント。ユーザースペースでは同じですが、キャンバススペースでは異なります。

BxByの値は、スケールファクターに関係なく常に一定であるため、わかります(この値は、キャンバススペースとユーザースペースの両方でわかります)。

ユーザースペースでAxAyを知っているので、AxからBxまでとAyからBy。この測定値はユーザースペースにあり、キャンバススペースの測定値に変換するには、単にスケール係数で除算します。 (キャンバススペースに変換されると、これらの線が赤、オレンジ、黄色で表示されます)。

ポイントBは一定であるため、ポイントとエッジの間の距離は一定です(これらは青い線で表されます)。この値は、ユーザースペースとキャンバススペースで同じです。

キャンバススペースでのキャンバスの幅がわかっているので、これら2つのキャンバススペースの測定値(AxからBxおよびBxからEdge)を減算します。キャンバススペースのポイントAの座標が残っている合計幅:

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

上記のコードと画像は、元の中央の左上をクリックしたときの説明です。同じロジックを使用して、Aがどの象限にあるかに関係なく、Aを見つけ、次のようにリファクタリングしました。

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

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

パラメータAxとAyは、ユーザーがonTouch()を介してタッチするポイントです。以前のスケーリング/変換された値を保持するために、MatrixContextクラスで静的マトリックスインスタンスを所有していました。

2
Keanu Pang

本当に申し訳ありませんが、これはラッシュでの簡単な答えです。しかし、私も最近これを見てきました-私は見つけました http://code.google.com/p/Android-multitouch-controller/ あなたがやりたいことをするために(私は思う-私はしなければならなかったスキムはあなたの投稿を読んでください)。お役に立てれば。これで問題が解決しない場合は、今夜適切に調べて、さらに支援できるかどうかを確認します。

1
Martyn