基于JScrollPane实现鼠标定点缩放的问题求助
问题描述
我找到了几个与我遇到的问题类似的提问,但由于我使用的是JScrollPane,这些问题的解答都无法真正解决我的问题。
我找到了定点缩放所需的公式代码:
originX -= mouseX / zoom - mouseX; originY -= mouseY / zoom - mouseY;
于是我尝试将其适配到JScrollPane中:
JScrollPane scrollPane = drawFrame.scrollPane; JScrollBar vert = scrollPane.getVerticalScrollBar(), horiz = scrollPane.getHorizontalScrollBar(); horiz.setValue( (int)(horiz.getValue() - (float)pastMouseX / zoom + pastMouseX) ); vert.setValue( (int)(vert.getValue() - (float)pastMouseY / zoom + pastMouseY) );
但缩放时鼠标指向的定点位置完全混乱,我不清楚原因。以下是最小可复现示例代码:
import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; public class ResizeTest extends JFrame{ JScrollPane scrollPane; BufferedImage image; int mouseX, mouseY; float zoom; public static void main(String[] args) { EventQueue.invokeLater(new Runnable(){ @Override public void run(){ new ResizeTest(); } }); } public ResizeTest(){ zoom = 1; try{ image = ImageIO.read(new File("a.jpg")); }catch(IOException e){ e.printStackTrace(); } JPanel panel = new JPanel(){ @Override public void paintComponent(Graphics g){ super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; g2.scale(zoom, zoom); g2.drawImage(image, 0, 0, null); System.out.println(zoom); } @Override public Dimension getPreferredSize(){ return new Dimension(image.getWidth(), image.getHeight()); } }; scrollPane = new JScrollPane(panel); panel.addMouseWheelListener(new MouseWheelListener(){ @Override public void mouseWheelMoved(MouseWheelEvent e) { if(e.isControlDown()) { if(e.getWheelRotation() > 0){ zoom /= 1.3333333333f; if(zoom < 0.125) zoom = 0.125f; } else if (e.getWheelRotation() < 0){ zoom *= 1.3333333333f; if(zoom > 10) zoom = 10; } if(zoom < 10){ JScrollBar vert = scrollPane.getVerticalScrollBar(), horiz = scrollPane.getHorizontalScrollBar(); horiz.setValue( (int)(horiz.getValue() - (float)mouseX / zoom + mouseX) ); vert.setValue( (int)(vert.getValue() - (float)mouseY / zoom + mouseY) ); } } else { scrollPane.dispatchEvent(e); } panel.repaint(); } }); panel.addMouseMotionListener(new MouseMotionListener(){ @Override public void mouseDragged(MouseEvent e) { mouseX = e.getX(); mouseY = e.getY(); } @Override public void mouseMoved(MouseEvent e) { mouseX = e.getX(); mouseY = e.getY(); } }); setSize(new Dimension(1200,700)); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); add(scrollPane); setVisible(true); } @Override public Dimension getPreferredSize(){ return new Dimension(1200, 700); } }
问题原因与解决方法
核心问题
- 鼠标坐标未转换:
mouseX、mouseY是面板上的像素坐标,和缩放后的图像实际坐标不匹配,直接代入公式会导致滚动条计算偏差。 - 滚动条逻辑错误:原始公式针对画布原点偏移,而JScrollPane滚动条值对应视图偏移量,需结合缩放比例变化重新推导,不能直接套用原始公式。
- 面板尺寸未适配缩放:
getPreferredSize返回原始图像尺寸,滚动条范围不会随缩放变化,导致滚动逻辑失效。
修正实现
关键调整点
- 先记录旧缩放比例,计算缩放变化量
- 将面板鼠标坐标转换为原始图像坐标系下的位置
- 重新推导滚动条计算公式,保证鼠标指向的图像位置固定
- 更新面板首选尺寸,适配缩放后的图像大小
修正后的完整代码
import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; public class ResizeTest extends JFrame{ JScrollPane scrollPane; BufferedImage image; int mouseX, mouseY; float zoom; public static void main(String[] args) { EventQueue.invokeLater(new Runnable(){ @Override public void run(){ new ResizeTest(); } }); } public ResizeTest(){ zoom = 1; try{ image = ImageIO.read(new File("a.jpg")); }catch(IOException e){ e.printStackTrace(); } JPanel panel = new JPanel(){ @Override public void paintComponent(Graphics g){ super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; g2.scale(zoom, zoom); g2.drawImage(image, 0, 0, null); } @Override public Dimension getPreferredSize(){ return new Dimension((int)(image.getWidth() * zoom), (int)(image.getHeight() * zoom)); } }; scrollPane = new JScrollPane(panel); panel.addMouseWheelListener(new MouseWheelListener(){ @Override public void mouseWheelMoved(MouseWheelEvent e) { if(e.isControlDown()) { float oldZoom = zoom; // 调整缩放比例 if(e.getWheelRotation() > 0){ zoom /= 1.3333333333f; zoom = Math.max(zoom, 0.125f); } else { zoom *= 1.3333333333f; zoom = Math.min(zoom, 10f); } // 转换鼠标坐标到原始图像坐标系 float imageMouseX = mouseX / oldZoom; float imageMouseY = mouseY / oldZoom; JScrollBar vert = scrollPane.getVerticalScrollBar(); JScrollBar horiz = scrollPane.getHorizontalScrollBar(); // 计算新滚动条位置,保证鼠标指向的图像点固定 int newHorizValue = (int)(imageMouseX * zoom - mouseX + (horiz.getValue() / oldZoom) * zoom); int newVertValue = (int)(imageMouseY * zoom - mouseY + (vert.getValue() / oldZoom) * zoom); horiz.setValue(newHorizValue); vert.setValue(newVertValue); // 更新滚动条范围 panel.revalidate(); } else { scrollPane.dispatchEvent(e); } panel.repaint(); } }); panel.addMouseMotionListener(new MouseMotionListener(){ @Override public void mouseDragged(MouseEvent e) { mouseX = e.getX(); mouseY = e.getY(); } @Override public void mouseMoved(MouseEvent e) { mouseX = e.getX(); mouseY = e.getY(); } }); setSize(new Dimension(1200,700)); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); add(scrollPane); setVisible(true); } @Override public Dimension getPreferredSize(){ return new Dimension(1200, 700); } }
内容的提问来源于stack exchange,提问作者The Dog on the Log
相关产品推荐
相关产品推荐

