4

作成中の Android アプリのキャンバス座標を取得しようとしています。スケール フォーカス ポイントを使用するコードを追加するまで (2 行に続く)、うまく機能します。

scalePoint.setX((int) detector.getFocusX());
scalePoint.setY((int) detector.getFocusY());

ビュークラスのソースコードは次のとおりです。

    package us.kniffin.Jigsaw;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;

public class TestView extends View {
    private float mLastTouchX;
    private float mLastTouchY;
    private float mPosX;
    private float mPosY;

    private Rect rect;

    private float cX, cY; // circle coords


    // Scaling objects
    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.f;
    // The focus point for the scaling
    private float scalePointX; 
    private float scalePointY;


    public TestView(Context context) {
        super(context);

        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    protected void onDraw(Canvas canvas) {      
        super.onDraw(canvas);
        Paint p = new Paint();
        p.setColor(Color.RED);

         rect = canvas.getClipBounds();

        canvas.save();
        canvas.scale(mScaleFactor, mScaleFactor, scalePointX, scalePointY);
        canvas.translate(mPosX, mPosY);
        canvas.drawCircle(cX, cY, 10, p);

        canvas.restore();

    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
         // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);

        final int action = ev.getAction();


        switch(action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN: {

            final float x = ev.getX()/mScaleFactor;// screen X position
            final float y = ev.getY()/mScaleFactor;// screen Y position

            cX = x - (rect.left * mScaleFactor) - mPosX; // canvas X
            cY = y - (rect.top * mScaleFactor) - mPosY; // canvas Y

            // Remember where we started
            mLastTouchX = x;
            mLastTouchY = y;
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            final float x = ev.getX()/mScaleFactor;
            final float y = ev.getY()/mScaleFactor;
            cX = x - (rect.left * mScaleFactor) - mPosX; // canvas X
            cY = y - (rect.top * mScaleFactor) - mPosY; // canvas Y


            // Only move if the ScaleGestureDetector isn't processing a gesture.
            if (!mScaleDetector.isInProgress()) {
                final float dx = x - mLastTouchX; // change in X
                final float dy = y - mLastTouchY; // change in Y

                mPosX += dx;
                mPosY += dy;

                invalidate();
            }

            mLastTouchX = x;
            mLastTouchY = y;

            break;

        }
        case MotionEvent.ACTION_UP: {
            mLastTouchX = 0;
            mLastTouchY = 0;
            invalidate();
        }
        }
        return true;
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();
            scalePointX =  detector.getFocusX();
            scalePointY = detector.getFocusY();

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

            invalidate();
            return true;
        }
    }


}

これを機能させるために私がする必要があるアイデアはありますか?

更新:コード サンプルを、同じ問題を抱えている別のサンプルに置き換えましたが、要点が簡略化されています。

再更新:スケーリング後に問題が発生します。拡大縮小する前は座標は正しいのですが、拡大縮小後は、クリックした場所の右と下に座標がずれすぎています。ズームアウトすればするほど、間違いが増えるようです。

4

3 に答える 3

8

ハハ!成功!ほぼ丸一日かかりましたが、たくさんの推測とチェックを行って理解しました。

必要なコードは次のとおりです。

case MotionEvent.ACTION_DOWN: {
            final float x = (ev.getX() - scalePointX)/mScaleFactor;
            final float y = (ev.getY() - scalePointY)/mScaleFactor;
            cX = x - mPosX + scalePointX; // canvas X
            cY = y - mPosY + scalePointY; // canvas Y
[snip]
}

ACTION_MOVE の同様のコード

于 2012-03-21T22:37:23.530 に答える
1

移動、スケーリング、回転などを使用するときはいつでも、キャンバスのマトリックスを変更しています。通常、この行列は、(x,y) ごとにキャンバス上のどこにあるかを示すために使用されます。キャンバスを作成すると、行列は恒等行列になります: (1 ) ( 1 ) ( 1) しかし、これらの変更のいずれかを行うたびに、この行列も変更されます。正しい座標を知りたい場合は、スケーリング/回転する前に canvas.save() を使用し、スケーリング後に canvas.restore() を使用する必要があります

于 2012-03-21T08:22:26.470 に答える
0

私は最近、非常に似たようなことをしているときにこの問題に遭遇し、いくつかの試行錯誤と多くのグーグル検索の後、この回答を適応させることになりました( https://stackoverflow.com/a/9945896/1131180 ):

(e は MotionEvent であるため、このコードを使用するのに最適な場所は onTouch または onLongPress です)

mClickCoords = new float[2];

//e is the motionevent that contains the screen touch we
//want to translate into a canvas coordinate
mClickCoords[0] = e.getX();
mClickCoords[1] = e.getY();

Matrix matrix = new Matrix();
matrix.set(getMatrix());

//this is where you apply any translations/scaling/rotation etc.
//Typically you want to apply the same adjustments that you apply
//in your onDraw().

matrix.preTranslate(mVirtualX, mVirtualY);
matrix.preScale(mScaleFactor, mScaleFactor, mPivotX, mPivotY);

// invert the matrix, creating the mapping from screen 
//coordinates to canvas coordinates
matrix.invert(matrix); 

//apply the mapping
matrix.mapPoints(mClickCoords);

//mClickCoords[0] is the canvas x coordinate and
//mClickCoords[1] is the y coordinate.

ここで適用できる明らかな最適化がいくつかありますが、この方法の方がより明確だと思います。

于 2013-05-24T21:21:08.757 に答える