【问题标题】:Canvas Pinch-Zoom to Point Within Bounds画布捏缩放到边界内的点
【发布时间】:2012-07-05 06:54:28
【问题描述】:

我在这个问题上被困了八个小时,所以我想是时候寻求帮助了。

在我开始我的问题之前,我会告诉大家我已经浏览过这个网站和谷歌,但我找到的答案都没有帮助。 (This 是一,anotheranother。)

这里是交易:我有一个类扩展SurfaceView(我们称之为MySurface)并覆盖其中的许多方法。通常,它会绘制几个正方形和文本框,这一切都很好。一旦用户开始触摸,它就会转换为Bitmap,然后绘制每一帧,直到用户释放为止。

问题来了:我想实现这样一个功能,用户可以将两根手指放在屏幕上,捏合缩放,也可以平移(但只能用两根手指向下)。

我找到了一些捏到缩放的实现,并通过以下方式将它们调整为MySurface 中的Canvas 对象:

public void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();

    canvas.scale(mScaleVector.z, mScaleVector.z); // This is the scale factor as seen below
    canvas.translate(mScaleVector.x, mScaleVector.y); // These are offset values from 0,0, both working fine

    // Start draw code

    // ...

    // End draw code

    canvas.restore();
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float factor = detector.getScaleFactor();
        if (Math.abs(factor - 1.0f) >= 0.0075f) {
            mScaleVector.z *= factor;
            mScaleVector.z = Math.max(MIN_ZOOM, Math.min(mScaleVector.z, MAX_ZOOM));
        }

        // ...

        invalidate();

        return true;
    }
}
public boolean onTouchEvent(MotionEvent event) {
    int action = event.getAction() & MotionEvent.ACTION_MASK;
    int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    if (event.getPointerCount() == 2) {
        if (action == MotionEvent.ACTION_POINTER_DOWN && pointerIndex == 1) {
            // The various pivot coordinate codes would belong here
        }
    }

    detector.onTouchEvent(event); // Calls the Scale Gesture Detector
    return true;
}

虽然这两个元素都可以正常工作——来回滚动和捏合缩放——但存在一个大问题。捏拉缩放在使用时会放大到点0,0,而不是放大到手指点。

我尝试了很多方法来解决这个问题:

  • 使用canvas.scale(mScaleVector.z, mScaleVector.z, mScaleVector.x, mScaleVector.y);;显然,这会产生不需要的结果,因为 mScaleVector x 和 y 值是 0 偏移量。
  • 管理使用与translate() 方法相同的偏移量的“枢轴”坐标,但这会产生相同的0,0 问题,或者在触摸视图时跳转。
  • 许多其他事情...我已经对上述枢轴坐标做了很多工作,尝试将其位置基于用户的第一次触摸,并在每个连续手势中相对于该触摸移动它。

此外,这个画布必须是有界的,所以用户不能永远滚动。但是,当我使用.scale(sx, sy, px, py) 方法时,它会将事情推到我在.translate() 中设置的任何范围之外。

我……在这一点上对任何事情都持开放态度。我知道可以添加此功能,就像在 Android 4.0 库中看到的那样(查看单个图像时)。我试图追踪处理此问题的源代码,但无济于事。

【问题讨论】:

  • 我个人直接使用onTouchEvent处理两个手指位置。有了这个,你可以很容易地找到中间点,以及需要多少缩放。
  • 如果您解决了我面临同样问题的问题,您能否发布解决方案,而我的客户是.... :(,或者您能告诉我您是如何解决问题的。谢谢你

标签: android android-canvas android-gesture


【解决方案1】:

这是我用来在ImageView 中使用ScaleGestureDetector 实现捏拉缩放的代码。只需很少或无需修改,您也应该能够使用它,因为您也可以使用转换 marices 来绘制 Canvas

@Override
public boolean onScale(ScaleGestureDetector detector) {
    float mScaleFactor = (float) Math.min(
        Math.max(.8f, detector.getScaleFactor()), 1.2);
    float origScale = saveScale;
    saveScale *= mScaleFactor;
    if (saveScale > maxScale) {
        saveScale = maxScale;
        mScaleFactor = maxScale / origScale;
    } else if (saveScale < minScale) {
        saveScale = minScale;
        mScaleFactor = minScale / origScale;
    }
    right = width * saveScale - width
            - (2 * redundantXSpace * saveScale);
    bottom = height * saveScale - height
            - (2 * redundantYSpace * saveScale);
    if (origWidth * saveScale <= width
            || origHeight * saveScale <= height) {
        matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2);
        if (mScaleFactor < 1) {
            matrix.getValues(m);
            float x = m[Matrix.MTRANS_X];
            float y = m[Matrix.MTRANS_Y];
            if (mScaleFactor < 1) {
                if (Math.round(origWidth * saveScale) < width) {
                    if (y < -bottom)
                        matrix.postTranslate(0, -(y + bottom));
                    else if (y > 0)
                        matrix.postTranslate(0, -y);
                } else {
                    if (x < -right)
                        matrix.postTranslate(-(x + right), 0);
                    else if (x > 0)
                        matrix.postTranslate(-x, 0);
                }
            }
        }
    } else {
        matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
        matrix.getValues(m);
        float x = m[Matrix.MTRANS_X];
        float y = m[Matrix.MTRANS_Y];
        if (mScaleFactor < 1) {
            if (x < -right)
                matrix.postTranslate(-(x + right), 0);
            else if (x > 0)
                matrix.postTranslate(-x, 0);
            if (y < -bottom)
                matrix.postTranslate(0, -(y + bottom));
            else if (y > 0)
                matrix.postTranslate(0, -y);
        }
    }
    return true;
}

在我的例子中,我在 View 的 onMeasure() 方法中计算了必要的值,您可能想在 SurfaceView 的其他地方执行此操作

width = MeasureSpec.getSize(widthMeasureSpec); // Change this according to your screen size
height = MeasureSpec.getSize(heightMeasureSpec); // Change this according to your screen size

// Fit to screen.
float scale;
float scaleX = (float) width / (float) bmWidth;
float scaleY = (float) height / (float) bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
setImageMatrix(matrix);
saveScale = 1f;
scaleMappingRatio = saveScale / scale;

// Center the image
redundantYSpace = (float) height - (scale * (float) bmHeight);
redundantXSpace = (float) width - (scale * (float) bmWidth);
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;

matrix.postTranslate(redundantXSpace, redundantYSpace);

origWidth = width - 2 * redundantXSpace;
origHeight = height - 2 * redundantYSpace;
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
setImageMatrix(matrix);

一点解释:

saveScaleBitmap的当前缩放比例

mScaleFactor 是您必须乘以比例的因子。

maxScaleminScale 可以是常量值。

widthheight 是屏幕的尺寸。

redundantXSpaceredundantYSpace 是图像边框和屏幕边框之间的空白,因为当图像小于屏幕时,图像居中

origHeightorigWidth 是位图的大小

matrix是当前用于绘制位图的变换矩阵

诀窍是,当我在初始化时第一次缩放图像并将其居中时,我选择了比例为 1,并使用scaleMappingRatio 映射了图像的实际比例值。

【讨论】:

  • 哦,伙计,在审查和彻底测试您的代码方面后,我发现这些矩阵转换是关键!如果可以的话,我会请你喝啤酒! A+,好先生。
  • 不客气,我也花了很多时间寻找正确的方法,当我开始编写上面的代码时,我认为值得分享。
【解决方案2】:
    protected override void OnDraw(Canvas canvas)
    {
        canvas.Save();

        int x = (int)(canvas.Width * mScaleFactor - canvas.Width) / 2;
        int y = (int)(canvas.Height * mScaleFactor - canvas.Height) / 2;

        if (mPosX > (canvas.Width + x - 50))
        {
            mPosX = canvas.Width + x - 50;
        }
        if (mPosX < (50 - canvas.Width - x))
        {
            mPosX = 50 - canvas.Width - x;
        }
        if (mPosY > (canvas.Height + y - 50))
        {
            mPosY = canvas.Height + y - 50;
        }
        if (mPosY < (50 - canvas.Height - y))
        {
            mPosY = 50 - canvas.Height - y;
        }

        canvas.Translate(mPosX, mPosY);
        canvas.Scale(mScaleFactor, mScaleFactor, canvas.Width/2, canvas.Height/2);
        base.OnDraw(canvas);
        canvas.Restore();
    }

这是 monodroid 代码,但可以很容易地转换为 JAVA。 您可以为您的边界选择任何值(这里是 50 像素边距)

【讨论】:

猜你喜欢
  • 2018-11-01
  • 2013-07-11
  • 2018-06-24
  • 2023-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-27
相关资源
最近更新 更多