我有一个可以用来画画的 jpanel。我希望能够使用鼠标滚轮进行放大和缩小,但我想缩放到鼠标的位置,以便鼠标下方的点保持不变。我在 stackoverflow 上发现了一些问题,但它们对我不起作用。
通过实现这里描述的内容,我已经非常接近做我想做的事情了。这是我的代码:
public class MyPanel extends JPanel {
...
private double zoom = 1;
private double zoom_old = 1;
private int zoomPointX;
private int zoomPointY;
...
class CustomMouseWheelListener implements MouseWheelListener {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
zoomPointX = e.getX();
zoomPointY = e.getY();
if (e.getPreciseWheelRotation() < 0) {
zoom -= 0.1;
} else {
zoom += 0.1;
}
if (zoom < 0.01) {
zoom = 0.01;
}
repaint();
}
}
...
protected void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
if (zoom != zoom_old) {
double scalechange = zoom - zoom_old;
zoom_old = zoom;
double offsetX = -(zoomPointX * scalechange);
double offsetY = -(zoomPointY * scalechange) ;
AffineTransform at = new AffineTransform();
at.scale(zoom, zoom);
at.translate(offsetX, offsetY);
g2D.setTransform(at);
}
a_different_class_where_i_do_some_drawing.draw(g2D);
}
}
这几乎达到了我想要的效果。如果我尝试缩放,我会注意到鼠标的位置被考虑在内,因此例如如果我将鼠标放在面板的左侧,它将大致在左侧放大。但是,它不会精确地缩放到鼠标上,因此鼠标下方的点仍然会发生变化。
谁能帮我解决这个问题吗?
编辑: 下面是上面发布的代码所发生情况的图片:我首先将鼠标放在蓝色方块上,然后用鼠标滚轮滚动。正如你所看到的,如果改变鼠标位置:
我通过实现所描述的内容解决了问题这里
这是更新后的代码:
public class MyPanel extends JPanel {
...
private double zoom = 1;
private int zoomPointX;
private int zoomPointY;
...
class CustomMouseWheelListener implements MouseWheelListener {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
zoomPointX = e.getX();
zoomPointY = e.getY();
if (e.getPreciseWheelRotation() < 0) {
zoom -= 0.1;
} else {
zoom += 0.1;
}
if (zoom < 0.01) {
zoom = 0.01;
}
repaint();
}
}
...
protected void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
AffineTransform at = g2D.getTransform();
at.translate(zoomPointX, zoomPointY);
at.scale(zoom, zoom);
at.translate(-zoomPointX, -zoomPointY);
g2D.setTransform(at);
a_different_class_where_i_do_some_drawing.draw(g2D);
}
}
如果有人好奇如何做到这一点,这是另一个解决方案。我的程序编写方式不同,所以我必须采取不同的方法。
前提有点简单,但有一些陷阱。我将尝试解释下面的代码是如何工作的。您很可能无法复制和粘贴,因此了解其工作原理最为重要。
public void mouseWheelMoved(MouseWheelEvent e) {
Point2D before = null;
Point2D after = null;
AffineTransform originalTransform = new AffineTransform(at);
AffineTransform zoomedTransform = new AffineTransform();
try {
before = originalTransform.inverseTransform(e.getPoint(), null);
} catch (NoninvertibleTransformException e1) {
e1.printStackTrace();
}
zoom *= 1-(e.getPreciseWheelRotation()/5);
((Painter) Frame1.painter).setScale(zoom/100);
zoomedTransform.setToIdentity();
zoomedTransform.translate(getWidth()/2, getHeight()/2);
zoomedTransform.scale(scale, scale);
zoomedTransform.translate(-getWidth()/2, -getHeight()/2);
zoomedTransform.translate(translateX, translateY);
try {
after = zoomedTransform.inverseTransform(e.getPoint(), null);
} catch (NoninvertibleTransformException e1) {
e1.printStackTrace();
}
double deltaX = after.getX() - before.getX();
double deltaY = after.getY() - before.getY();
translate(deltaX,deltaY);
Frame1.painter.repaint();
}
这些变量将保存变换后的鼠标位置。
Point2D before = null;
Point2D after = null;
这是什么意思?
e.getPoint() 给出鼠标的 PIXEL 位置。我们需要鼠标所在位置的“世界”坐标。如果我们将鼠标悬停在位置 (50,40) 处的框原点上,我们需要这些值,而不是鼠标位置值。
例如如果我们以 1 倍缩放/比例将面板沿 X 方向平移 10 像素,则当鼠标悬停在 (50,40) 的框上时,鼠标位置将为 (40,40)。当您缩放和平移时,这会变得更加复杂。
为了更容易获得“世界”坐标,我们需要 inverseTransform() 函数。为此,我们需要在缩放之前保存当前的 AffineTransform。 ('at'是我程序中的全局AffineTransform)
AffineTransform originalTransform = new AffineTransform(at);
然后我们将创建另一个变换,但这是一个恒等变换,它还没有被缩放或翻译。
AffineTransform zoomedTransform = new AffineTransform();
一旦我们有了这些,我们就可以获得鼠标的“世界”坐标(在缩放之前)。 (如上所述50,40)
before = originalTransform.inverseTransform(e.getPoint(), null);
// before.getX() returns 50;
// before.getY() returns 40;
现在我们根据鼠标滚动事件设置比例值。
zoom *= 1-(e.getPreciseWheelRotation()/5);
/// This line just calls a function to set the variable 'scale'
((Painter) Frame1.painter).setScale(zoom/100);
现在我们想要进行NEW恒等变换,并使用旧的平移值但使用新的比例值对其进行变换。 (注意:这必须以与 Paint() 函数中完全相同的方式执行。)
zoomedTransform.setToIdentity();
zoomedTransform.translate(getWidth()/2, getHeight()/2);
zoomedTransform.scale(scale, scale);
zoomedTransform.translate(-getWidth()/2, -getHeight()/2);
zoomedTransform.translate(translateX, translateY);
注意:我们原来的Transform是缩放/缩放之前的变换。 ZoomedTransform 是在应用我们新的缩放/缩放之后。
现在我们已经放大了,假设我们的鼠标现在位于 20,30 处不同框原点的上方。我们的鼠标位置没有改变,我们所做的只是滚动鼠标滚轮。我们想要做的是获取鼠标位置并获取 20,30 坐标。我们通过对新变换进行逆变换来做到这一点。
after = zoomedTransform.inverseTransform(e.getPoint(), null);
因此,“之前”= (50,40) 和“之后”= (20,30) 我们现在想通过这种差异来翻译视图。这会将框移动到鼠标位置下方 50,40 处。这将产生放大到 50,40 处的框的效果,而不仅仅是放大到屏幕中心。
double deltaX = after.getX() - before.getX();
double deltaY = after.getY() - before.getY();
translate(deltaX,deltaY);
然后我们重新粉刷。
Frame1.painter.repaint();
作为参考,这里是我的绘制函数中的代码。请注意平移和缩放的应用方式与 mouseWheel 函数中的应用方式完全相同。
public void paint(Graphics g) {
loadColors();
super.paint(g);
Graphics2D g2D = (Graphics2D) g;
//Set anti-alias!
g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Set anti-alias for text
g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
AffineTransform saveTransform = g2D.getTransform();
g2D.setColor(backgroundColor);
g2D.fillRect(0, 0, getWidth(), getHeight());
at = new AffineTransform(saveTransform);
if(newTruss) {
try {
centerView();
} catch (NoninvertibleTransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
newTruss = false;
}
at.translate(getWidth()/2, getHeight()/2);
at.scale(scale, scale);
at.translate(-getWidth()/2, -getHeight()/2);
at.translate(translateX, translateY);
g2D.setTransform(at);
if(truss != null) {
g2D.setColor(Color.WHITE);
//truss.drawBoardsOutline(g2D);
truss.drawBoards(g2D, boardColor, lineColor);
truss.drawBoardsLongitudinalCOG(g2D, pointColor, 3);
truss.drawBoardsCOG(g2D, Color.GREEN, 1);
truss.drawSelectedBoards(g2D, Frame1.boardList, boardColor, Color.RED, lastSelectedBoard);
if (lastSelectedBoard > -1) {
truss.drawBoardPath(g2D, lastSelectedBoard, lastSelectedBoardDir, 50.0f, 7);
//System.out.println(Frame1.placementList);
}
//truss.drawBoardPaths(g2D, 50.0f, 7);
}
}