我该如何对画布的平移矩阵设置限制?

9

我正在扩展一个定制的SurfaceView,并尝试使其具有缩放和滚动功能。

如何滚动:

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
            float distanceX, float distanceY) {

        // subtract scrolled amount
        matrix.postTranslate(-distanceX, -distanceY);

        rebound();

        invalidate();

        // handled
        return true;
    }

如何进行缩放:

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (detector.isInProgress()) {
            // get scale 
            float factor = detector.getScaleFactor();

            // Don't let the object get too small or too large.
            if (factor * scale > 1f) {
                factor = 1f / scale;
            } else if (factor * scale < minScale) {
                factor = minScale / scale;
            }

            // store local scale
            scale *= factor;

            // do the scale
            matrix.preScale(factor, factor, detector.getFocusX(), detector.getFocusY());

            rebound();

            invalidate();
        }

        return true;
    }
(供参考,我在onDraw中使用以下代码:)
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();
    canvas.setMatrix(matrix);
    // [...] stuff drawn with matrix settings
    canvas.restore();
    // [...] stuff drawn without matrix settings, as an overlay
}

目前这两种方法都运行良好。缩放在最小值(在0f和1f之间)和最大值(当前始终为1f)的比例值上正确停止。

使用的全局字段:

  • drawWdrawH = float,表示缩放为1时画布数据的像素大小。
  • parentWparentH = float,表示可见视图的像素大小。
  • matrix = android.graphics.Matrix

问题在于我正在尝试实现的“rebound()”方法(也许需要一个更好的名称,呵呵),它会自动强制内容保持在视图中。

我已经尝试了各种方法来计算边界应该在哪里(在矩形(0、0、parentW、parentH)内),并在矩阵“超出范围”时将其“转回去”。

这是我目前拥有的东西,肯定不起作用,而是将其推得更远。我觉得问题在于我的数学,而不是我的想法。请有人能否提供更简单或更清晰的代码,将矩阵转换到边缘,如果它太远和/或修复我的实现尝试的问题?用于使其出现在左上角的if检查。

public void rebound() {
    // bounds
    RectF currentBounds = new RectF(0, 0, drawW, drawH);
    matrix.mapRect(currentBounds);
    RectF parentBounds = new RectF(0, 0, parentW, parentH/2);

    PointF diff = new PointF(0, 0);

    if (currentBounds.left > parentBounds.left) {
        diff.x += (parentBounds.left - currentBounds.left);
    }
    if (currentBounds.top > parentBounds.top) {
        diff.y += (parentBounds.top - currentBounds.top);
    }
    if (currentBounds.width() > parentBounds.width()) {
        if (currentBounds.right < parentBounds.right) {
            diff.x += (parentBounds.right - currentBounds.right);
        }
        if (currentBounds.bottom < parentBounds.bottom) {
            diff.y += (parentBounds.bottom - currentBounds.bottom);
        }
    }

    matrix.postTranslate(diff.x, diff.y);
}

我之前写的一个版本在矩阵成为一个字段之前(我只是在onDraw中使用了canvas.scale()canvas.translate()),它是可以工作的:

public void rebound() {
    // bounds
    int boundTop = 0;
    int boundLeft = 0;
    int boundRight = (int)(-scale * drawW + parentW);
    int boundBottom = (int)(-scale * drawH + parentH);

    if (boundLeft >= boundRight) {
        mScrollX = Math.min(boundLeft, Math.max(boundRight, mScrollX));
    } else {
        mScrollX = 0;
    }
    if (boundTop >= boundBottom) {
        mScrollY = Math.min(boundTop, Math.max(boundBottom, mScrollY));
    } else {
        mScrollY = 0;
    }
}

我正在使用新的方法,以便能够在detector.getFocusX()detector.getFocusY()为中心正确缩放。

更新:我将方法更改为现在的样子。它仍然有点问题,y方向的边界偏离中心太远,在更改缩放级别后也是错误的。我还将其设置为“preScale”和“postTranslate”,这样(据我所知)它应该始终应用比例,然后再进行平移,而不是混合它们。

最终更新:现在它可以工作了。以下是一个带注释的可行的rebound方法:

public void rebound() {
    // make a rectangle representing what our current canvas looks like
    RectF currentBounds = new RectF(0, 0, drawW, drawH);
    matrix.mapRect(currentBounds);
    // make a rectangle representing the scroll bounds
    RectF areaBounds = new RectF((float) getLeft(),
                                   (float) getTop(),
                                   (float) parentW + (float) getLeft(),
                                   (float) parentH + (float) getTop());

    // the difference between the current rectangle and the rectangle we want
    PointF diff = new PointF(0f, 0f);

    // x-direction
    if (currentBounds.width() > areaBounds.width()) {
        // allow scrolling only if the amount of content is too wide at this scale
        if (currentBounds.left > areaBounds.left) {
            // stop from scrolling too far left
            diff.x = (areaBounds.left - currentBounds.left);
        }
        if (currentBounds.right < areaBounds.right) {
            // stop from scrolling too far right
            diff.x = (areaBounds.right - currentBounds.right);
        }
    } else {
        // negate any scrolling
        diff.x = (areaBounds.left - currentBounds.left);
    }

    // y-direction
    if (currentBounds.height() > areaBounds.height()) {
        // allow scrolling only if the amount of content is too tall at this scale
        if (currentBounds.top > areaBounds.top) {
            // stop from scrolling too far above
            diff.y = (areaBounds.top - currentBounds.top);
        }
        if (currentBounds.bottom < areaBounds.bottom) {
            // stop from scrolling too far below
            diff.y = (areaBounds.bottom - currentBounds.bottom);
        }
    } else {
        // negate any scrolling
        diff.y = (areaBounds.top - currentBounds.top);
    }

    // translate
    matrix.postTranslate(diff.x, diff.y);
}
它通过将滚动反向转换回边界来消除我不想要的任何滚动。如果内容太小,它将完全取消滚动,强制内容位于左上角。
1个回答

1

我曾经实现过类似的东西来自己编写捏合缩放功能。我猜测你的 y 轴偏移问题可能是由于视图不是全屏大小,或者你可能没有改变坐标以匹配比例。

我的实现计算了一个比例因子,并使用以下方式应用它: canvas.scale( pinchZoomScale, pinchZoomScale ); 然后计算屏幕的物理尺寸(以像素为单位),将其转换为米,并最终应用缩放因子,以便所有绘制的对象都能正确偏移。

这个实现依赖于始终知道屏幕中心应该是什么,并锁定它,无论缩放级别如何。


你是正确的!视图不是整个屏幕大小。矩阵变换是相对于屏幕而不是画布视图吗?我以为相对于画布而不是屏幕会更有意义... - Ribose
矩阵相对于画布而言,但锚定在0,0位置,因此将比例加倍并绘制到5,5位置会使其看起来在2.5,2.5位置。我发现在某些情况下,视图大小可能会混淆,并尝试使用屏幕大小作为指南,而不是您正在操作的实际视图。为了使画布缩放与画布上对象位置的缩放协调起来,需要进行大量的试错。 - ScouseChris
1
谢谢!经过许多的试错,我终于让它成功了。 - Ribose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,