缩放和平移后更正触摸事件的坐标 canvas
Correcting coordinates of touch events after zooming and paning canvas
使用缩放时,可以通过除以 ScaleFactor 来校正 MotionEvent 坐标。
此外,缩放和平移时,除以比例因子并减去偏移量。
然而,在处理缩放时,就没那么容易了。除法确实得到了正确的相对坐标,但是因为涉及到平移,所以0不是0。0可以是-2000的偏移量。
那么我如何才能更正 TouchEvents 以在缩放和平移后给出正确的坐标?
代码:
缩放:
class Scaler extends ScaleGestureDetector {
public Scaler(Context context, OnScaleGestureListener listener) {
super(context, listener);
}
@Override
public float getScaleFactor() {
return super.getScaleFactor();
}
}
class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener{
@Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
if(scaleFactor > 2) scaleFactor = 2;
else if(scaleFactor < 0.3f) scaleFactor = 0.3f;
scaleFactor = ((float)((int)(scaleFactor * 100))) / 100;//jitter-protection
scaleMatrix.setScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {return true;}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
System.out.println("ScaleFactor: " + scaleFactor);
}
}
触摸事件:
@Override
public boolean onTouchEvent(MotionEvent ev) {
int pointers = ev.getPointerCount();
if(pointers == 2 ) {
zoom = true;
s.onTouchEvent(ev);
}else if(pointers == 1 && zoom){
if(ev.getAction() == MotionEvent.ACTION_UP)
zoom = false;
return true;
}else {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//scaled physical coordinates
x = ev.getX() /*/ mScaleFactorX*/;//unscaled
y = ev.getY() /*/ mScaleFactorY*/;
sx = ev.getX() / scaleFactor;//scaled
sy = ev.getY() / scaleFactor;
//////////////////////////////////////////
tox = toy = true;
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
if (tox && toy) {
x = ev.getX() /*/ mScaleFactorX*/;
y = ev.getY() /*/ mScaleFactorY*/;
sx = ev.getX() / scaleFactor;
sy = ev.getY() / scaleFactor;
System.out.println("XY: " + sx + "/" + sy);
Rect cursor = new Rect((int) x, (int) y, (int) x + 1, (int) y + 1);
Rect scaledCursor = new Rect((int)sx, (int)sy, (int)sx+1, (int)sy+1);
...
}
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
//This is where the pan happens.
float currX = ev.getX() / scaleFactor;
float currY = ev.getY() / scaleFactor;
float newOffsetX = (sx - currX),
newOffsetY = (sy - currY);
if (newOffsetY < Maths.convertDpToPixel(1, c) && newOffsetY > -Maths.convertDpToPixel(1, c))
newOffsetY = 0;
else tox = false;
if (newOffsetX < Maths.convertDpToPixel(1, c) && newOffsetX > -Maths.convertDpToPixel(1, c))
newOffsetX = 0;
else toy = false;
this.newOffsetX = newOffsetX;
this.newOffsetY = newOffsetY;
offsetX += newOffsetX;
offsetY += newOffsetY;
sx = ev.getX() / scaleFactor;
sy = ev.getY() / scaleFactor;
}
}
return true;
}
缩放矩阵的实现:
Matrix scaleMatrix = new Matrix();
public void render(Canvas c) {
super.draw(c);
if (c != null) {
backgroundRender(c);
c.setMatrix(scaleMatrix);
//Example rendering:
c.drawRect(0 - offsetX,0 - offsetY,10 - offsetX,10 - offsetY,paint);
c.setMatrix(null);//null the matrix to allow for unscaled rendering after this line. For UI objects.
}
}
问题是,当缩放 0 时,对象的坐标不会移动。表示对象呈现在例如-2500,-2500 将呈现为超过 0,0。它们的坐标与 TouchEvent 不同。那么如何纠正触摸事件呢?
我试过的:
这会导致缩放延迟和对象飞走。 ev = onTouchEvent 中的 MotionEvent。不校正坐标
Matrix invert = new Matrix(scaleMatrix);
invert.invert(invert);
ev.transform();
这不起作用,因为坐标与对象相比是错误的。坐标 < 0 的对象显示超过 0,这意味着 MotionEvents 无论如何都是错误的。
int sx = ev.getX() / scaleFactor;//same with y, but ev.getY()
经过大量研究后找到了解决方案
每当获取缩放坐标时,获取canvas的clipBounds并将顶部和左侧坐标添加到X/Y坐标:
sx = ev.getX() / scaleFactor + clip.left;
sy = ev.getY() / scaleFactor + clip.top ;
clip
是一个 Rect,定义为 Canvas.
的 clipBounds
public void render(Canvas c) {
super.draw(c);
if (c != null) {
c.setMatrix(scaleMatrix);
clip = c.getClipBounds();
(...)
}
}
使用缩放时,可以通过除以 ScaleFactor 来校正 MotionEvent 坐标。
此外,缩放和平移时,除以比例因子并减去偏移量。
然而,在处理缩放时,就没那么容易了。除法确实得到了正确的相对坐标,但是因为涉及到平移,所以0不是0。0可以是-2000的偏移量。
那么我如何才能更正 TouchEvents 以在缩放和平移后给出正确的坐标?
代码:
缩放:
class Scaler extends ScaleGestureDetector {
public Scaler(Context context, OnScaleGestureListener listener) {
super(context, listener);
}
@Override
public float getScaleFactor() {
return super.getScaleFactor();
}
}
class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener{
@Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
if(scaleFactor > 2) scaleFactor = 2;
else if(scaleFactor < 0.3f) scaleFactor = 0.3f;
scaleFactor = ((float)((int)(scaleFactor * 100))) / 100;//jitter-protection
scaleMatrix.setScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {return true;}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
System.out.println("ScaleFactor: " + scaleFactor);
}
}
触摸事件:
@Override
public boolean onTouchEvent(MotionEvent ev) {
int pointers = ev.getPointerCount();
if(pointers == 2 ) {
zoom = true;
s.onTouchEvent(ev);
}else if(pointers == 1 && zoom){
if(ev.getAction() == MotionEvent.ACTION_UP)
zoom = false;
return true;
}else {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//scaled physical coordinates
x = ev.getX() /*/ mScaleFactorX*/;//unscaled
y = ev.getY() /*/ mScaleFactorY*/;
sx = ev.getX() / scaleFactor;//scaled
sy = ev.getY() / scaleFactor;
//////////////////////////////////////////
tox = toy = true;
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
if (tox && toy) {
x = ev.getX() /*/ mScaleFactorX*/;
y = ev.getY() /*/ mScaleFactorY*/;
sx = ev.getX() / scaleFactor;
sy = ev.getY() / scaleFactor;
System.out.println("XY: " + sx + "/" + sy);
Rect cursor = new Rect((int) x, (int) y, (int) x + 1, (int) y + 1);
Rect scaledCursor = new Rect((int)sx, (int)sy, (int)sx+1, (int)sy+1);
...
}
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
//This is where the pan happens.
float currX = ev.getX() / scaleFactor;
float currY = ev.getY() / scaleFactor;
float newOffsetX = (sx - currX),
newOffsetY = (sy - currY);
if (newOffsetY < Maths.convertDpToPixel(1, c) && newOffsetY > -Maths.convertDpToPixel(1, c))
newOffsetY = 0;
else tox = false;
if (newOffsetX < Maths.convertDpToPixel(1, c) && newOffsetX > -Maths.convertDpToPixel(1, c))
newOffsetX = 0;
else toy = false;
this.newOffsetX = newOffsetX;
this.newOffsetY = newOffsetY;
offsetX += newOffsetX;
offsetY += newOffsetY;
sx = ev.getX() / scaleFactor;
sy = ev.getY() / scaleFactor;
}
}
return true;
}
缩放矩阵的实现:
Matrix scaleMatrix = new Matrix();
public void render(Canvas c) {
super.draw(c);
if (c != null) {
backgroundRender(c);
c.setMatrix(scaleMatrix);
//Example rendering:
c.drawRect(0 - offsetX,0 - offsetY,10 - offsetX,10 - offsetY,paint);
c.setMatrix(null);//null the matrix to allow for unscaled rendering after this line. For UI objects.
}
}
问题是,当缩放 0 时,对象的坐标不会移动。表示对象呈现在例如-2500,-2500 将呈现为超过 0,0。它们的坐标与 TouchEvent 不同。那么如何纠正触摸事件呢?
我试过的:
这会导致缩放延迟和对象飞走。 ev = onTouchEvent 中的 MotionEvent。不校正坐标
Matrix invert = new Matrix(scaleMatrix);
invert.invert(invert);
ev.transform();
这不起作用,因为坐标与对象相比是错误的。坐标 < 0 的对象显示超过 0,这意味着 MotionEvents 无论如何都是错误的。
int sx = ev.getX() / scaleFactor;//same with y, but ev.getY()
经过大量研究后找到了解决方案
每当获取缩放坐标时,获取canvas的clipBounds并将顶部和左侧坐标添加到X/Y坐标:
sx = ev.getX() / scaleFactor + clip.left;
sy = ev.getY() / scaleFactor + clip.top ;
clip
是一个 Rect,定义为 Canvas.
public void render(Canvas c) {
super.draw(c);
if (c != null) {
c.setMatrix(scaleMatrix);
clip = c.getClipBounds();
(...)
}
}