我有一个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)
}