鼠标位置相对缩放正常,放大时绘制文本越界如何修复?
问题描述
已实现基于鼠标位置的滚轮缩放功能,JScrollPane会随缩放自动调整尺寸,但放大过度时,绘制的“Example”文本部分会超出边界。推测问题出在Drawing类paintComponent方法中的平移操作——该操作保证了正常缩放,但导致放大时文本越界,求修复方案。
最小可复现代码
TestPane类
package testpane; import java.awt.event.MouseAdapter; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.WindowConstants; public class TestPane { public static Drawing d; public static double zoomFactor = 1; public static void main(String[] args) { JFrame f = new JFrame("Tree Diagram"); f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); f.getContentPane().add(new TestPane().makeDiagram()); f.setSize(1600, 800); f.setLocationRelativeTo(null); f.setVisible(true); } public JComponent makeDiagram() { d = new Drawing(); MouseAdapter mouseAdapter = new TestPaneMouseListener(); d.addMouseListener(mouseAdapter); d.addMouseMotionListener(mouseAdapter); d.addMouseWheelListener(mouseAdapter); return new JScrollPane(d); } }
TestPaneMouseListener类
package testpane; import java.awt.Component; import java.awt.Container; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import javax.swing.JComponent; import javax.swing.JViewport; import javax.swing.SwingUtilities; import static testpane.TestPane.d; import static testpane.TestPane.zoomFactor; public class TestPaneMouseListener extends MouseAdapter { private final Point origin = new Point(); @Override public void mouseDragged(MouseEvent e) { Component c = e.getComponent(); Container p = SwingUtilities.getUnwrappedParent(c); if (p instanceof JViewport) { JViewport viewport = (JViewport) p; Point cp = SwingUtilities.convertPoint(c, e.getPoint(), viewport); Point vp = viewport.getViewPosition(); vp.translate(origin.x - cp.x, origin.y - cp.y); ((JComponent) c).scrollRectToVisible(new Rectangle(vp, viewport.getSize())); origin.setLocation(cp); } } @Override public void mousePressed(MouseEvent e) { Component c = e.getComponent(); Container p = SwingUtilities.getUnwrappedParent(c); if(p instanceof JViewport) { JViewport viewport = (JViewport) p; Point cp = SwingUtilities.convertPoint(c, e.getPoint(), viewport); origin.setLocation(cp); } } @Override public void mouseWheelMoved(MouseWheelEvent e) { if(e.getWheelRotation()<0) { zoomFactor*=1.05; d.setZoomFactor(1.05); } if(e.getWheelRotation()>0) { zoomFactor/=1.05; d.setZoomFactor(1/1.05); } } }
Drawing类
package testpane; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.MouseInfo; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import javax.swing.JPanel; public class Drawing extends JPanel { private final AffineTransform zoomTransform = new AffineTransform(); private final Rectangle rect = new Rectangle(1600, 800); private double xOffset = 0; private double yOffset = 0; private double prevZoomFactor = 1; public Drawing() { Font currentFont = getFont(); Font newFont = currentFont.deriveFont(currentFont.getSize() * 15F); setFont(newFont); setBackground(Color.WHITE); } public void setZoomFactor(double zoomFactor) { zoomTransform.scale(zoomFactor, zoomFactor); revalidate(); repaint(); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); AffineTransform scrollTransform = g2d.getTransform(); double xRel = MouseInfo.getPointerInfo().getLocation().getX() - getLocationOnScreen().getX(); double yRel = MouseInfo.getPointerInfo().getLocation().getY() - getLocationOnScreen().getY(); double zoomDiv = TestPane.zoomFactor / prevZoomFactor; xOffset = (zoomDiv) * (xOffset) + (1 - zoomDiv) * xRel; yOffset = (zoomDiv) * (yOffset) + (1 - zoomDiv) * yRel; prevZoomFactor = TestPane.zoomFactor; scrollTransform.translate(xOffset, yOffset); scrollTransform.concatenate(zoomTransform); g2d.setTransform(scrollTransform); g2d.drawString("Example", 400, 400); g2d.dispose(); } @Override public Dimension getPreferredSize() { Rectangle r = zoomTransform.createTransformedShape(rect).getBounds(); return new Dimension(r.width, r.height); } }
修复方案
问题根源
当前paintComponent中的平移计算会累积偏移量,把缩放时的视图中心偏移和组件自身的绘制偏移混淆了,导致绘制内容被错误地“推”出视图边界。正确的做法是:通过调整JViewport的视图位置实现鼠标中心缩放,组件绘制保持原始坐标基准,不额外添加平移。
修改后的代码
调整TestPaneMouseListener类(处理视图位置)
package testpane; import java.awt.Component; import java.awt.Container; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import javax.swing.JComponent; import javax.swing.JViewport; import javax.swing.SwingUtilities; import static testpane.TestPane.d; import static testpane.TestPane.zoomFactor; public class TestPaneMouseListener extends MouseAdapter { private final Point origin = new Point(); @Override public void mouseDragged(MouseEvent e) { Component c = e.getComponent(); Container p = SwingUtilities.getUnwrappedParent(c); if (p instanceof JViewport) { JViewport viewport = (JViewport) p; Point cp = SwingUtilities.convertPoint(c, e.getPoint(), viewport); Point vp = viewport.getViewPosition(); vp.translate(origin.x - cp.x, origin.y - cp.y); ((JComponent) c).scrollRectToVisible(new Rectangle(vp, viewport.getSize())); origin.setLocation(cp); } } @Override public void mousePressed(MouseEvent e) { Component c = e.getComponent(); Container p = SwingUtilities.getUnwrappedParent(c); if(p instanceof JViewport) { JViewport viewport = (JViewport) p; Point cp = SwingUtilities.convertPoint(c, e.getPoint(), viewport); origin.setLocation(cp); } } @Override public void mouseWheelMoved(MouseWheelEvent e) { double oldZoom = zoomFactor; // 更新缩放因子 if(e.getWheelRotation() < 0) { zoomFactor *= 1.05; } else if(e.getWheelRotation() > 0) { zoomFactor /= 1.05; } double scale = zoomFactor / oldZoom; // 获取鼠标在组件内的原始坐标 Point mouseInComponent = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), d); JViewport viewport = (JViewport) SwingUtilities.getUnwrappedParent(d); Point viewPos = viewport.getViewPosition(); // 计算新的视图位置:保证鼠标点在屏幕上的位置不变 int newX = (int)(mouseInComponent.x * scale - (mouseInComponent.x - viewPos.x)); int newY = (int)(mouseInComponent.y * scale - (mouseInComponent.y - viewPos.y)); d.setZoomFactor(scale); // 调整视图位置,实现鼠标中心缩放 viewport.setViewPosition(new Point(newX, newY)); } }
简化Drawing类(移除多余平移)
package testpane; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import javax.swing.JPanel; public class Drawing extends JPanel { private final AffineTransform zoomTransform = new AffineTransform(); private final Rectangle rect = new Rectangle(1600, 800); public Drawing() { Font currentFont = getFont(); Font newFont = currentFont.deriveFont(currentFont.getSize() * 15F); setFont(newFont); setBackground(Color.WHITE); } public void setZoomFactor(double zoomFactor) { zoomTransform.scale(zoomFactor, zoomFactor); revalidate(); repaint(); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); // 只应用缩放变换,无需额外平移 g2d.transform(zoomTransform); g2d.drawString("Example", 400, 400); g2d.dispose(); } @Override public Dimension getPreferredSize() { Rectangle r = zoomTransform.createTransformedShape(rect).getBounds(); return new Dimension(r.width, r.height); } }
关键修改点
- 将缩放时的视图位置调整逻辑从组件绘制层移到鼠标事件处理层,直接操作
JViewport.setViewPosition,保证组件绘制坐标始终基于原始原点。 - 移除Drawing类中累积的偏移量计算,避免绘制内容被错误平移出边界。
- 鼠标滚轮事件中,通过计算缩放前后鼠标点的坐标关系,确保缩放后鼠标位置对应的内容保持在屏幕同一位置,实现平滑的中心缩放。
内容的提问来源于stack exchange,提问作者Some Ace
相关产品推荐
相关产品推荐

