Неправильная позиция масштаба после применения эффекта масштабирования на холсте Android

Прежде всего, это дополнительный вопрос, изначально заданный здесь: панорамирование, масштабирование и масштабирование пользовательского представления для рисования на холсте в Android

Так как ответа пока не было, я наконец-то решил проблему с помощью детекторов жестов Android

После применения жеста масштабирования / масштабирования я обнаружил, что координаты рисования холста по-прежнему указывают на старую позицию (до применения масштабирования), а не рисуют точно такие же сенсорные координаты. По сути, я не могу получить правильные координаты холста после масштабирования или перетаскивания холста.

Перед увеличением

введите описание изображения здесь

После уменьшения масштаба точки касания рисуют на прежнем месте. Я хочу, чтобы он рисовал на текущем месте касания,

введите описание изображения здесь

Образец кода,

public class DrawingView extends View {

    private void setupDrawing() {

        mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());

        mgd = new MoveGestureDetector(ctx, mgl);
        sgd = new ScaleGestureDetector(ctx, sgl);
        rgd = new RotateGestureDetector(ctx, rgl);

}

class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();
            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
            invalidate();
            return true;
        }
    }

    MoveGestureDetector.SimpleOnMoveGestureListener mgl = new MoveGestureDetector.SimpleOnMoveGestureListener() {
        @Override
        public boolean onMove(MoveGestureDetector detector) {
            PointF delta = detector.getFocusDelta();
            matrix.postTranslate(delta.x, delta.y);
            invalidate();
            return true;
        }
    };

    ScaleGestureDetector.SimpleOnScaleGestureListener sgl = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float scale = detector.getScaleFactor();
            matrix.postScale(scale, scale, detector.getFocusX(), detector.getFocusY());
            invalidate();
            return true;
        }
    };

    RotateGestureDetector.SimpleOnRotateGestureListener rgl = new RotateGestureDetector.SimpleOnRotateGestureListener() {
        @Override
        public boolean onRotate(RotateGestureDetector detector) {
            matrix.postRotate(-detector.getRotationDegreesDelta(), detector.getFocusX(), detector.getFocusY());
            invalidate();
            return true;
        }
    };

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        //view given size
        super.onSizeChanged(w, h, oldw, oldh);
        canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        drawCanvas = new Canvas(canvasBitmap);
    }

    private void touch_start(float x, float y) {
        undonePaths.clear();
        drawPath.reset();
        drawPath.moveTo(x, y);
        mX = x;
        mY = y;
    }

    private void touch_move(float x, float y, float x2, float y2) {
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
           /* QUad to curves using a quadratic line (basically an ellipse of some sort).
           LineTo is a straight line. QuadTo will smooth out jaggedies where they turn.
          */
            drawPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }

    }

    private void touch_up() {

            drawPath.lineTo(mX, mY);
            // commit the path to our offscreen
            drawCanvas.drawPath(drawPath, drawPaint);
            // kill this so we don't double draw
            paths.add(drawPath);
            drawPath = new Path();
            drawPath.reset();
            invalidate();
    }

@Override
    public boolean onTouchEvent(MotionEvent event) {

        if (isZoomable) {
            mgd.onTouchEvent(event);
            sgd.onTouchEvent(event);
            rgd.onTouchEvent(event);
        }

        if (!isTouchable) {
            return super.onTouchEvent(event);
        } else {
            //detect user touch
            float x = event.getX();
            float y = event.getY();

            switch (event.getAction() & MotionEvent.ACTION_MASK) {

                case MotionEvent.ACTION_DOWN:
                    if (!isZoomable) {
                        touch_start(x, y);
                    }
                    invalidate();
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (!isZoomable) {
                        //mPositions.add(new Vector2(x - mBitmapBrushDimensions.x / 2, y - mBitmapBrushDimensions.y / 2));
                        if (isCustomBrush && mBitmapBrushDimensions != null) {
                            mPositions = new Vector2(x - mBitmapBrushDimensions.x / 2, y - mBitmapBrushDimensions.y / 2);
                            touch_move(x, y, x - mBitmapBrushDimensions.x / 2, y - mBitmapBrushDimensions.y / 2);
                        } else {
                            touch_move(x, y, 0, 0);
                        }
                    }
                    invalidate();
                    break;

                case MotionEvent.ACTION_UP:
                    if (!isZoomable) {
                        touch_up();
                    }
                    invalidate();
                    break;
            }
            mScaleDetector.onTouchEvent(event);
            return true;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();

        canvas.setMatrix(matrix);

        for (Path p : paths) {
                canvas.drawPath(p, drawPaint);
                drawPaint.setColor(selectedColor);
                drawPaint.setStrokeWidth(brushSize);
                canvas.drawPath(drawPath, drawPaint);
        }
        canvas.restore();
    }
}

PS: MoveGestureDetector (), ScaleGestureDetector () и RotateGestureDetector() - это пользовательские классы, унаследованные от android-жест-детекторов

2 ответа

Вот что я сделал. По сути, вы должны найти разницу между "старыми" и новыми точками. Перейти к основанию для важных строк...

@Override
public boolean onScale(ScaleGestureDetector detector) {

    scaleFactor *= detector.getScaleFactor();

    float xDiff = initialFocalPoints[0] - currentFocalPoints[0];
    float yDiff = initialFocalPoints[1] - currentFocalPoints[1];

    transformMatrix.setScale(scaleFactor, scaleFactor, 
                                 currentFocalPoints[0], currentFocalPoints[1]);
    transformMatrix.postTranslate(xDiff, yDiff);    
    child.setImageMatrix(transformMatrix);

    return true;
}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector){

    float startX = detector.getFocusX() + getScrollX();
    float startY = detector.getFocusY() + getScrollY();

    initialFocalPoints = new float[]{startX, startY};

    if(transformMatrix.invert(inverseTransformMatrix))
    inverseTransformMatrix.mapPoints(currentFocalPoints, initialFocalPoints);
    return true;
}

Линии, которые имели значение, были следующими:

float xDiff = initialFocalPoints[0] - currentFocalPoints[0];
float yDiff = initialFocalPoints[1] - currentFocalPoints[1];
transformMatrix.postTranslate(xDiff, yDiff);

Ответ был так же прост, как выяснить разницу между двумя точками и переводить изображение при каждом масштабировании изображения.

Чтобы применить любое преобразование, вы должны понимать правила математики. Работает как для двухмерной, так и для трехмерной графики. То есть, если вы работаете с матрицами перемещения (T), вращения (R), масштаба (S), чтобы применить любое преобразование, сначала у вас есть масштабный объект (умножьте координаты xyz на эту матрицу S), а затем поверните (мульт. На R) затем сдвиньте объект на T. Итак, вы применяете вращение над некоторой точкой, вам нужно переместить объект в ноль и масштабировать, а затем вернуться к базовой точке. То есть именно в вашем случае, прежде чем применять масштаб, вы должны сдвинуть (уменьшить) все координаты по позиции касания, затем применить матрицу масштабирования путем умножения, а затем сдвинуть, увеличив все позиции этим касанием.

Другие вопросы по тегам