在使用枢轴点进行画布缩放后,x和y坐标错误 [英] after canvas zoom with pivot point, x- and y- coordinates are wrong

查看:247
本文介绍了在使用枢轴点进行画布缩放后,x和y坐标错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在一个应该集中在一个枢轴点的画布上实现缩放。缩放工作正常,但之后用户应该能够选择画布上的元素。问题是,我的翻译值似乎是不正确的,因为他们有一个不同的偏移,我不放大到枢轴点(缩放没有枢轴点和拖动工作正常)。
我使用了来自此示例



相关代码为:

  class DragView extends View {

private static float MIN_ZOOM = 0.2f;
private static float MAX_ZOOM = 2f;

//这些常数指定我们的模式
private static int NONE = 0;
private int mode = NONE;
private static int DRAG = 1;
private static int ZOOM = 2;
public ArrayList< ProcessElement>元素;

//可视化
private boolean checkDisplay = false;
private float displayWidth;
private float displayHeight;
//这两个变量跟踪手指的X和Y坐标,当它第一次
//触摸屏幕
private float startX = 0f;
private float startY = 0f;
//这两个变量跟踪我们需要沿着X
//和Y坐标翻译canvas的数量
//也是从初始的偏移0,0
private float translateX = 0f;
私人浮动translateY = 0f;

private float lastGestureX = 0;
private float lastGestureY = 0;

private float scaleFactor = 1.f;
private ScaleGestureDetector检测器;
...

private void sharedConstructor(){
elements = new ArrayList< ProcessElement>();
flowElements = new ArrayList< ProcessFlow>();
detector = new ScaleGestureDetector(getContext(),new ScaleListener());
}

/ **
*检查一次以获得测量的屏幕高度/宽度
* @param hasWindowFocus
* /
@覆盖
public void onWindowFocusChanged(boolean hasWindowFocus){
super.onWindowFocusChanged(hasWindowFocus);
if(!checkDisplay){
displayHeight = getMeasuredHeight();
displayWidth = getMeasuredWidth();
checkDisplay = true;
}
}

@Override
public boolean onTouchEvent(MotionEvent event){
ProcessBaseElement lastElement = null;

switch(event.getAction()& MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
mode = DRAG;

//检查元素是否被触摸。
//需要使用绝对位置,这就是我们考虑偏移量的原因
touchedElement = isElementTouched((translateX * -1)+ event.getX())/ scaleFactor,(translateY * -1 + event.getY())/ scaleFactor);


//我们将手指的当前X和Y坐标分配给startX和startY减去以前翻译的
//每个坐标的数量这个工作甚至在我们翻译时第一次,因为这两个变量的初始
//值为零。
startX = event.getX() - translateX;
startY = event.getY() - translateY;
}
//如果一个元素被触摸了 - >没有必要考虑补偿,因为没有可能拖动
else {
startX = event.getX();
startY = event.getY();
}

break;

case MotionEvent.ACTION_MOVE:
if(mode!= ZOOM){
if(touchedElement == null){
translateX = event.getX() - startX ;
translateY = event.getY() - startY;
} else {
startX = event.getX();
startY = event.getY();
}
}

if(detect.isInProgress()){
lastGestureX = detector.getFocusX();
lastGestureY = detector.getFocusY();
}

break;

case MotionEvent.ACTION_UP:
mode = NONE;

break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = ZOOM;

break;
case MotionEvent.ACTION_POINTER_UP:
break;
}

detect.onTouchEvent(event);
invalidate();

return true;
}

private ProcessBaseElement isElementTouched(float x,float y){
for(int i = elements.size() - 1; i> = 0; i-- )$ {
if(elements.get(i).isTouched(x,y))
return elements.get(i);
}
return null;
}

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

canvas.save();

if(detect.isInProgress()){
canvas.scale(scaleFactor,scaleFactor,detector.getFocusX(),detector.getFocusY());
} else
canvas.scale(scaleFactor,scaleFactor,lastGestureX,lastGestureY); // zoom

// canvas.scale(scaleFactor,scaleFactor);

//我们需要在这里除以比例因子,否则我们最终会基于缩放级别
//进行过多的平移,因为翻译量也会根据我们的放大了画布。
canvas.translate(translateX / scaleFactor,translateY / scaleFactor);

drawContent(canvas);

canvas.restore();
}

/ **
*缩放画布
* /
私人类ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector){
scaleFactor * = detector.getScaleFactor();
scaleFactor = Math.max(MIN_ZOOM,Math.min(scaleFactor,MAX_ZOOM));
return true;
}
}
}

在画布上的位置(考虑到拖动)。我怀疑我不考虑新的偏移从枢轴点到 translateX translateY ,但我无法确定在哪里和如何我应该这样做。
任何帮助将不胜感激。

解决方案

好吧,所以你基本上试图弄清楚某个屏幕X / Y坐标对应于视图围绕某个关键点{Px,Py}缩放后。



因此,让我们尝试将其分解。



为了论证起见,假设Px& Py = 0,并且s = 2。这意味着视图在视图左上角的左上角缩放了2倍。



屏幕坐标{0,0}对应于视图中的{0,0},因为该点是唯一没有改变的点。一般来说,如果屏幕坐标等于旋转点,则没有变化。



如果用户点击某个点,会发生什么,让{2} ,3}?在这种情况下,一次{2,3}现在从枢轴点(其为{0,0})移动了2倍,因此对应的位置为{4,6}。



当枢轴点为{0,0}时,这很容易,但当不是时,会发生什么?



让我们看另一种情况 - 枢轴点现在是视图的右下角(Width = w,Height = h - {w,h})。再次,如果用户在相同位置点击,则对应位置也是{w,h},但是让我们说用户点击某个其他位置,例如{w-2,h-3}?同样的逻辑发生在这里:翻译的位置是{w - 4,h - 6}。



要概括,我们要做的是转换屏幕坐标到翻译的坐标。我们需要对我们收到的X / Y坐标执行相同的操作,我们对缩放视图中的每个像素执行此操作。



第1步 - 我们想根据枢轴点翻译X / Y位置:

  X = X  -  Px 
Y = Y - Py

第2步 ; Y:

  X = X * s 
Y = Y * s



第3步 - 然后我们翻译回:

  X = X + Px 
Y = Y + Py

如果我们将它应用到我给出的最后一个示例(我将只演示为X):

 原始值:X = w -  2,Px = w 
步骤1:X <-X-Px = w-2-w = -2
步骤2:X <-X * s = -2 * 2 = -4
步骤3:X <-X + Px = -4 + w =​​ w-4


b $ b

将此项应用于在缩放之前相关的任何X / Y,该点将被翻译为相对于缩放状态。



希望这有助于。


I am trying to implement zooming on a canvas which should focus on a pivot point. Zooming works fine, but afterwards the user should be able to select elements on the canvas. The problem is, that my translation values seem to be incorrect, because they have a different offset, than the ones where I don't zoom to the pivot point (zoom without pivot point and dragging works fine). I used some code from this example.

The relevant code is:

class DragView extends View {

private static float MIN_ZOOM = 0.2f;
private static float MAX_ZOOM = 2f;

// These constants specify the mode that we're in
private static int NONE = 0;
private int mode = NONE;
private static int DRAG = 1;
private static int ZOOM = 2;
public ArrayList<ProcessElement> elements;

// Visualization
private boolean checkDisplay = false;
private float displayWidth;
private float displayHeight;
// These two variables keep track of the X and Y coordinate of the finger when it first
// touches the screen
private float startX = 0f;
private float startY = 0f;
// These two variables keep track of the amount we need to translate the canvas along the X
//and the Y coordinate
// Also the offset from initial 0,0
private float translateX = 0f;
private float translateY = 0f;

private float lastGestureX = 0;
private float lastGestureY = 0;

private float scaleFactor = 1.f;
private ScaleGestureDetector detector;
...

private void sharedConstructor() {
    elements = new ArrayList<ProcessElement>();
    flowElements = new ArrayList<ProcessFlow>();
    detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}

/**
 * checked once to get the measured screen height/width
 * @param hasWindowFocus
 */
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
    super.onWindowFocusChanged(hasWindowFocus);
    if (!checkDisplay) {
        displayHeight = getMeasuredHeight();
        displayWidth = getMeasuredWidth();
        checkDisplay = true;
    }
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    ProcessBaseElement lastElement = null;

    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            mode = DRAG;

            // Check if an Element has been touched.
            // Need to use the absolute Position that's why we take the offset into consideration
            touchedElement = isElementTouched(((translateX * -1) + event.getX()) / scaleFactor, (translateY * -1 + event.getY()) / scaleFactor);


                //We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
                //amount for each coordinates This works even when we are translating the first time because the initial
                //values for these two variables is zero.
                startX = event.getX() - translateX;
                startY = event.getY() - translateY;
            }
            // if an element has been touched -> no need to take offset into consideration, because there's no dragging possible
            else {
                startX = event.getX();
                startY = event.getY();
            }

            break;

        case MotionEvent.ACTION_MOVE:
            if (mode != ZOOM) {
                if (touchedElement == null) {
                    translateX = event.getX() - startX;
                    translateY = event.getY() - startY;
                } else {
                    startX = event.getX();
                    startY = event.getY();
                }
            }

            if(detector.isInProgress()) {
                lastGestureX = detector.getFocusX();
                lastGestureY = detector.getFocusY();
            }

            break;

        case MotionEvent.ACTION_UP:
            mode = NONE;

            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            mode = ZOOM;

            break;
        case MotionEvent.ACTION_POINTER_UP:
            break;
    }

    detector.onTouchEvent(event);
    invalidate();

    return true;
}

private ProcessBaseElement isElementTouched(float x, float y) {
    for (int i = elements.size() - 1; i >= 0; i--) {
        if (elements.get(i).isTouched(x, y))
            return elements.get(i);
    }
    return null;
}

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

    canvas.save();

    if(detector.isInProgress()) {
        canvas.scale(scaleFactor,scaleFactor,detector.getFocusX(),detector.getFocusY());
    } else
        canvas.scale(scaleFactor, scaleFactor,lastGestureX,lastGestureY);     // zoom

//        canvas.scale(scaleFactor,scaleFactor);

    //We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
    //because the translation amount also gets scaled according to how much we've zoomed into the canvas.
    canvas.translate(translateX / scaleFactor, translateY / scaleFactor);

    drawContent(canvas);

    canvas.restore();
}

/**
 * scales the canvas
 */
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        scaleFactor *= detector.getScaleFactor();
        scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
        return true;
    }
}
}

Elements are saved with their absolute position on the canvas (with dragging in mind). I suspect that I don't take the new offset from the pivot point to translateX and translateY in consideration, but I can't figure out where and how I should do this. Any help would be appreciated.

解决方案

Okay, so you're basically trying to figure out where a certain screen X/Y coordinate corresponds to, after the view has been scaled (s) around a certain pivot point {Px, Py}.

So, let's try to break it down.

For the sake of argument, lets assume that Px & Py = 0, and that s = 2. This means the view was zoomed by a factor of 2, around the top left corner of the view.

In this case, the screen coordinate {0, 0} corresponds to {0, 0} in the view, because that point is the only point which hasn't changed. Generally speaking, if the screen coordinate is equal to the pivot point, then there is no change.

What happens if the user clicks on some other point, lets say {2, 3}? In this case, what was once {2, 3} has now moved by a factor of 2 from the pivot point (which is {0, 0}), and so the corresponding position is {4, 6}.

All this is easy when the pivot point is {0, 0}, but what happens when it's not?

Well, lets look at another case - the pivot point is now the bottom right corner of the view (Width = w, Height = h - {w, h}). Again, if the user clicks at the same position, then the corresponding position is also {w, h}, but lets say the user clicks on some other position, for example {w - 2, h - 3}? The same logic occurs here: The translated position is {w - 4, h - 6}.

To generalize, what we're trying to do is convert the screen coordinates to the translated coordinate. We need to perform the same action on this X/Y coordinate we received that we performed on every pixel in the zoomed view.

Step 1 - we'd like to translate the X/Y position according to the pivot point:

X = X - Px
Y = Y - Py

Step 2 - Then we scale X & Y:

X = X * s
Y = Y * s

Step 3 - Then we translate back:

X = X + Px
Y = Y + Py

If we apply this to the last example I gave (I will only demonstrate for X):

Original value: X = w - 2, Px = w
Step 1: X <-- X - Px = w - 2 - w = -2
Step 2: X <-- X * s = -2 * 2 = -4
Step 3: X <-- X + Px = -4 + w = w - 4

Once you apply this to any X/Y you receive which is relevant prior to the zoom, the point will be translated so that it is relative to the zoomed state.

Hope this helps.

这篇关于在使用枢轴点进行画布缩放后,x和y坐标错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆