在 Android 画布上应用缩放效果后,缩放位置错误

Wrong scale position after applying zoom effect on Android canvas

提问人:Falling Into Infinity 提问时间:8/26/2016 最后编辑:CommunityFalling Into Infinity 更新时间:9/10/2016 访问量:2251

问:

首先,这是最初在这里提出的后续问题,在 Android 中平移、缩放和缩放 Canvas 绘图的自定义视图

由于还没有答案,我终于使用 android-gesture-detectors 解决了我的问题

应用缩放/缩放手势后,我发现画布绘图坐标仍然指向旧位置(在应用缩放之前),并且没有在完全相同的触摸坐标上绘图。基本上,缩放或拖动画布后,我无法获得正确的画布坐标。

在缩放之前,

enter image description here

缩小后,触摸点将绘制在上一个位置上。我希望它绘制在当前的触摸位置,

enter image description here

示例代码,

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-gesture-detectors 继承的自定义类

安卓 android-canvas android-view android-gesture android-touch-event

评论


答:

1赞 Vyacheslav 9/3/2016 #1

要应用任何变换,您必须了解数学规则。它适用于 2dim 和 3 维图形。 也就是说,如果您使用平移 (T)、旋转 (R)、缩放 (S) 矩阵来应用任何变换,则首先缩放对象(将坐标 xyz 乘以该矩阵 S),然后旋转(乘以 R),然后将对象移动 T。 因此,您在某个点上应用旋转,您必须将对象移动到零并缩放,然后返回基点。 也就是说,在您的情况下,在应用刻度之前,您必须通过触摸位置移动(减少)所有坐标,然后通过乘法应用刻度矩阵,然后通过此触摸增加所有位置来移动。

评论

0赞 Falling Into Infinity 9/3/2016
你介意举一个代码示例吗?正如我之前提到的,这是一个后续问题,所以你可以给出你自己缩放和缩放画布的想法。
3赞 kburbach 9/10/2016 #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);

答案很简单,就是弄清楚两点之间的差异,并在每次缩放图像时转换图像视图。