You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPanel实现2D网格缩放功能时鼠标移动出现抖屏bug如何修复

问题原因

抖动问题来自三个核心逻辑错误:

  • 鼠标移动事件中每次都将变换锚点tranx/trany设置为当前鼠标坐标,相当于每移动一次鼠标就偏移一次整个网格坐标系,放大后偏移量被缩放系数成倍放大,直接表现为画面剧烈抖动
  • 网格捕捉计算直接使用屏幕坐标系的鼠标坐标,没有转换为缩放后的网格本地坐标,捕捉逻辑完全错位
  • 在绘制方法paintComponent中修改全局zoom变量,会引发不必要的数值波动

修复方案

针对以上问题做如下调整:

  1. 仅在鼠标滚轮缩放时更新变换锚点,实现围绕鼠标位置缩放的效果,鼠标移动时不再修改锚点
  2. 鼠标坐标通过AffineTransform逆转换为网格本地坐标后,再执行网格捕捉计算
  3. 将zoom的精度处理逻辑挪到滚轮事件中,不在绘制逻辑里修改全局变量
  4. 统一坐标变换逻辑,避免跨坐标系计算错误

修复后完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 16:36:02