正确设置图像缩放的焦点

时间:2012-02-15 06:01:17

标签: android scaling surfaceview multi-touch

我有一个SurfaceView,可以将Bitmap作为背景绘制,另一个将用作叠加层。所以我决定使用可以用于两个位图的Matrix进行所有转换,因为它(我认为)是不使用OpenGL的最快方法之一。

我已经能够实现平移和缩放,但我遇到了一些问题:

  • 我无法找到如何专注于两者中心的方法 手指在缩放时,图像始终重置为其初始状态 (即,没有平移或剥落)在新规模出现之前 应用。除了看错,这也不允许用户缩放 看到整个图像,然后放大部分 重要。
  • 在scalling操作后,图像不会出现在 新抽奖通过后的同一个地方,因为翻译价值会 与众不同。

有没有办法使用Matrix实现这个目标还是有另一种解决方案?

代码如下(我在单独的线程中使用SurfaceHolder确实锁定SurfaceView画布并调用其doDraw方法):

public class MapSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

    public void doDraw(Canvas canvas) {
        canvas.drawColor(Color.BLACK);
        canvas.drawBitmap(mBitmap, mTransformationMatrix, mPaintAA);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_POINTER_DOWN: {
                if (event.getPointerCount() == 2) {
                    mOriginalDistance = MathUtils.distanceBetween(event.getX(0), event.getX(1), event.getY(0), event.getY(1));
                    mScreenMidpoint = MathUtils.midpoint(event.getX(0), event.getX(1), event.getY(0), event.getY(1));
                    mImageMidpoint = MathUtils.midpoint((mXPosition+event.getX(0))/mScale, (mXPosition+event.getX(1))/mScale, (mYPosition+event.getY(0))/mScale, (mYPosition+event.getY(1))/mScale);
                    mOriginalScale = mScale;
                }
            }
            case MotionEvent.ACTION_DOWN: {
                mOriginalTouchPoint = new Point((int)event.getX(), (int)event.getY());
                mOriginalPosition = new Point(mXPosition, mYPosition);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (event.getPointerCount() == 2) {
                    final double currentDistance = MathUtils.distanceBetween(event.getX(0), event.getX(1), event.getY(0), event.getY(1));
                    if (mIsZooming || currentDistance - mOriginalDistance > mPinchToZoomTolerance || mOriginalDistance - currentDistance > mPinchToZoomTolerance) {
                        final float distanceRatio = (float) (currentDistance / mOriginalDistance);
                        float tempZoom = mOriginalScale * distanceRatio;

                        mScale = Math.min(10, Math.max(Math.min((float)getHeight()/(float)mBitmap.getHeight(), (float)getWidth()/(float)mBitmap.getWidth()), tempZoom));
                        mScale = (float) MathUtils.roundToDecimals(mScale, 1);
                        mIsZooming = true;
                        mTransformationMatrix = new Matrix();
                        mTransformationMatrix.setScale(mScale, mScale);//, mImageMidpoint.x, mImageMidpoint.y);
                    } else {
                        System.out.println("Dragging");
                        mIsZooming = false;
                        final int deltaX = (int) ((int) (mOriginalTouchPoint.x - event.getX()));
                        final int deltaY = (int) ((int) (mOriginalTouchPoint.y - event.getY()));
                        mXPosition = mOriginalPosition.x + deltaX;  
                        mYPosition = mOriginalPosition.y + deltaY;
                        validatePositions();
                        mTransformationMatrix = new Matrix();
                        mTransformationMatrix.setScale(mScale, mScale);
                        mTransformationMatrix.postTranslate(-mXPosition, -mYPosition);
                    }
                }
                break;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP: {
                mIsZooming = false;
                validatePositions();
                mTransformationMatrix = new Matrix();
                mTransformationMatrix.setScale(mScale, mScale);
                mTransformationMatrix.postTranslate(-mXPosition, -mYPosition);
            }
        }
        return true;
    }

    private void validatePositions() {
        // Lower right corner
        mXPosition = Math.min(mXPosition, (int)((mBitmap.getWidth() * mScale)-getWidth()));
        mYPosition = Math.min(mYPosition, (int)((mBitmap.getHeight() * mScale)-getHeight()));
        // Upper left corner
        mXPosition = Math.max(mXPosition, 0);
        mYPosition = Math.max(mYPosition, 0);
        // Image smaller than the container, should center it
        if (mBitmap.getWidth() * mScale <= getWidth()) {
            mXPosition = (int) -((getWidth() - (mBitmap.getWidth() * mScale))/2);
        }
        if (mBitmap.getHeight() * mScale <= getHeight()) { 
            mYPosition = (int) -((getHeight() - (mBitmap.getHeight() * mScale))/2);
        }
    }
}

1 个答案:

答案 0 :(得分:2)

不是每次使用新的Matrix()重置转换矩阵,而是尝试使用post *()更新它。这样,您只进行相对于屏幕的操作。用术语来思考更容易:“在屏幕上缩放到这一点”。

现在有些代码了。在缩放部分计算了mScale:

...
mScale = (float) MathUtils.roundToDecimals(mScale, 1);
float ratio = mScale / mOriginalScale;
mTransformationMatrix.postScale(ratio, ratio, mScreenMidpoint.x, mScreenMidpoint.y);

在每个缩放触摸事件上重新计算mScreenMidpoint可能更好。这将允许用户在缩放时稍微改变焦点。对我来说,比前两个手指触摸后焦点冻结更自然。

在拖动过程中,您使用deltaX和deltaY而不是绝对点进行翻译:

mTransformationMatrix.postTranslate(-deltaX, -deltaY);

当然,您现在必须将validatePositions()方法更改为:

  • 确保deltaX和deltaY不会使图像移动太多,或
  • 使用转换矩阵检查图像是否在屏幕外,然后将其移至计数器

我将描述第二种方法,因为它更灵活,也允许验证缩放。

我们计算屏幕外的图像数量,然后使用这些值移动它:

void validate() {

    mTransformationMatrix.mapRect(new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()));

    float height = rect.height();
    float width = rect.width();

    float deltaX = 0, deltaY = 0;

    // Vertical delta
    if (height < mScreenHeight) {
        deltaY = (mScreenHeight - height) / 2 - rect.top;
    } else if (rect.top > 0) {
        deltaY = -rect.top;
    } else if (rect.bottom < mScreenHeight) {
        deltaY = mScreenHeight - rect.bottom;
    }

    // Horziontal delta
    if (width < mScreenWidth) {
        deltaX = (mScreenWidth - width) / 2 - rect.left;
    } else if (rect.left > 0) {
        deltaX = -rect.left;
    } else if (rect.right < mScreenWidth) {
        deltaX = mScreenWidth - rect.right;
    }

    mTransformationMatrix.postTranslate(deltaX, deltaY)
}