正确设置图像缩放的焦点



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

我已经能够实现平移和缩放,但我对我的有一些问题

  • 我没能找到一种方法来专注于两者的中心手指缩放时,图像总是重置为初始状态(也就是说,不平移也不缩放)应用除了看起来不对之外,这不允许用户缩放向外查看整个图像,然后放大重要
  • 缩放操作后,图像将不在新绘制过程后的相同位置,因为转换值将与众不同

有没有一种方法可以使用矩阵实现这一点,或者有其他解决方案?

代码如下(我在一个单独的线程中使用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);
        }
    }
}

与其每次使用new 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);

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

  • 确保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)
}

最新更新