JPanel实现2D网格缩放功能时鼠标移动出现抖屏bug如何修复
问题原因
抖动问题来自三个核心逻辑错误:
- 鼠标移动事件中每次都将变换锚点
tranx/trany设置为当前鼠标坐标,相当于每移动一次鼠标就偏移一次整个网格坐标系,放大后偏移量被缩放系数成倍放大,直接表现为画面剧烈抖动 - 网格捕捉计算直接使用屏幕坐标系的鼠标坐标,没有转换为缩放后的网格本地坐标,捕捉逻辑完全错位
- 在绘制方法
paintComponent中修改全局zoom变量,会引发不必要的数值波动
修复方案
针对以上问题做如下调整:
- 仅在鼠标滚轮缩放时更新变换锚点,实现围绕鼠标位置缩放的效果,鼠标移动时不再修改锚点
- 鼠标坐标通过AffineTransform逆转换为网格本地坐标后,再执行网格捕捉计算
- 将zoom的精度处理逻辑挪到滚轮事件中,不在绘制逻辑里修改全局变量
- 统一坐标变换逻辑,避免跨坐标系计算错误
修复后完整代码
import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagLayout; import java.awt.RenderingHints; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; public class DrawExample extends JFrame { private static final long serialVersionUID = 1L; private double zoom = 1d; private double tranx = 0; private double trany = 0; DrawPanel drawPanel; int snapmousepositionx, snapmousepositiony; int currentmousepositionx,currentmousepositiony; private AffineTransform currentTransform; public DrawExample() { drawPanel = new DrawPanel(); JPanel containerPanel = new JPanel(); JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setSize(800, 800); containerPanel.setLayout(new GridBagLayout()); containerPanel.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(1, 0, 1))); containerPanel.add(drawPanel); frame.add(new JScrollPane(containerPanel)); drawPanel.addMouseWheelListener(new MouseAdapter() { @Override public void mouseWheelMoved(MouseWheelEvent e) { // 先将当前鼠标点转换为网格本地坐标 Point2D mouseInGrid = null; if (currentTransform != null) { try { mouseInGrid = currentTransform.createInverse().transform(e.getPoint(), null); } catch (NoninvertibleTransformException ex) { ex.printStackTrace(); } } // 处理缩放系数 if (e.getPreciseWheelRotation() < 0) { zoom += 0.1; } else { zoom -= 0.1; } if (zoom < 0.01) { zoom = 0.01; } zoom = Math.round(zoom * 10.0) / 10.0; // 调整锚点实现围绕鼠标缩放 if (mouseInGrid != null) { Point2D newMousePos = currentTransform.transform(mouseInGrid, null); tranx += e.getX() - newMousePos.getX(); trany += e.getY() - newMousePos.getY(); } drawPanel.repaint(); } }); drawPanel.addMouseMotionListener(new MouseAdapter() { public void mouseMoved(MouseEvent me) { super.mouseMoved(me); // 将屏幕坐标转换为网格本地坐标后再做捕捉 Point2D gridPoint = null; if (currentTransform != null) { try { gridPoint = currentTransform.createInverse().transform(me.getPoint(), null); } catch (NoninvertibleTransformException e) { e.printStackTrace(); } } if (gridPoint != null) { drawPanel.createSnapGrid((int)gridPoint.getX(), (int)gridPoint.getY()); } drawPanel.repaint(); } }); frame.setVisible(true); } public class DrawPanel extends JPanel { @Override public Dimension getPreferredSize() { return new Dimension(800, 800); } @Override protected void paintComponent(Graphics grphcs) { super.paintComponent(grphcs); Graphics2D g2d = (Graphics2D) grphcs; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); AffineTransform at = new AffineTransform(); at.translate(tranx, trany); at.scale(zoom, zoom); g2d.setTransform(at); currentTransform = g2d.getTransform(); g2d.setColor(Color.lightGray); drawGrid(g2d); } public void createSnapGrid(int x, int y) { currentmousepositionx = x; currentmousepositiony = y; int remainderx = currentmousepositionx % 10, remaindery = currentmousepositiony % 10; if (remainderx < 5) setSnapX(currentmousepositionx - remainderx); else setSnapX(currentmousepositionx + (10 - remainderx)); if (remaindery < 5) setSnapY(currentmousepositiony - remaindery); else setSnapY(currentmousepositiony + (10 - remaindery)); } } public void drawGrid(Graphics2D g) { g.setColor(Color.lightGray); g.clearRect(0, 0, 800, 800); // 绘制网格竖线 for (int i = 10; i < 800; i += 10) { g.drawLine(i, 0, i, 800); } // 绘制网格横线 for (int j = 10; j < 800; j += 10) { g.drawLine(0, j, 800, j); } // 绘制捕捉点 g.setColor(Color.BLACK); if (getSnapX() >= 0 && getSnapY() >= 0 && getSnapX() <= 800 && getSnapY() <= 800) { g.drawOval(getSnapX() - 4, getSnapY() - 4, 8, 8); } } public int getSnapX(){ return this.snapmousepositionx; } public int getSnapY(){ return this.snapmousepositiony; } public void setSnapX(int snap){ this.snapmousepositionx = snap; } public void setSnapY(int snap){ this.snapmousepositiony = snap; } public static void main(String args[]) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new DrawExample(); } }); } }
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

