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

Java缩放面板图形操作:鼠标悬停时矩形变红实现

解决滚动面板缩放平移后的鼠标坐标转换与悬停高亮问题

你遇到的核心问题是没有正确反向应用绘图时的所有变换——除了缩放,你还忽略了paintComponent中为了居中画布添加的平移操作。下面是完整的解决方案,我会一步步解释关键修改点:

第一步:添加悬停跟踪变量

在Canvas类中添加一个变量,用来记录当前被鼠标悬停的矩形:

private Rectangle hoveredEntity = null;

第二步:抽离平移偏移计算方法

为了保证绘图和坐标转换用的偏移量完全一致,把paintComponent里的平移计算抽成单独方法:

private Point2D getTranslationOffset() {
    Rectangle size = getBounds();
    // 和paintComponent里的tx、ty计算完全一致
    double tx = ((size.getWidth() - workspace.getWidth() * zoom) / 2) / zoom;
    double ty = ((size.getHeight() - workspace.getHeight() * zoom) / 2) / zoom;
    return new Point2D.Double(tx, ty);
}

第三步:正确转换鼠标坐标到原始坐标系

修改mouseMoved方法,反向应用缩放和平移变换,得到原始工作区的坐标:

@Override
public void mouseMoved(MouseEvent e) {
    Point2D offset = getTranslationOffset();
    // 1. 先把屏幕坐标除以缩放因子,得到缩放后的画布坐标
    // 2. 再减去平移偏移,得到原始工作区的坐标
    double originalX = (e.getX() / zoom) - offset.getX();
    double originalY = (e.getY() / zoom) - offset.getY();
    Point2D originalPoint = new Point2D.Double(originalX, originalY);

    // 检查鼠标是否在实体矩形内
    if (entity1.contains(originalPoint)) {
        hoveredEntity = entity1;
    } else if (entity2.contains(originalPoint)) {
        hoveredEntity = entity2;
    } else {
        hoveredEntity = null;
    }
    repaint(); // 触发重绘,更新颜色
}

第四步:在绘图时根据悬停状态设置颜色

修改paintComponent方法,动态调整矩形的绘制颜色:

// 绘制实体矩形
g2d.setStroke(new BasicStroke(5.0f));
// 绘制第一个实体
if (hoveredEntity == entity1) {
    g2d.setColor(Color.RED);
} else {
    g2d.setColor(Color.DARK_GRAY);
}
g2d.draw(entity1);
// 绘制第二个实体
if (hoveredEntity == entity2) {
    g2d.setColor(Color.RED);
} else {
    g2d.setColor(Color.DARK_GRAY);
}
g2d.draw(entity2);

完整修改后的Canvas类代码

class Canvas extends JComponent implements MouseWheelListener, MouseMotionListener, MouseListener {
    private static final long serialVersionUID = 1L;
    private double zoom = 1.0;
    public static final double SCALE_STEP = 0.1d;
    private Dimension initialSize;
    private Point origin;
    private double previousZoom = zoom;
    private double scrollX = 0d;
    private double scrollY = 0d;

    private Rectangle2D workspace = new Rectangle2D.Double(0,0, 1024, 768);
    private Rectangle entity1 = new Rectangle(10, 10, 100, 100);
    private Rectangle entity2 = new Rectangle(300, 300, 100, 100);
    private Rectangle hoveredEntity = null; // 新增:跟踪悬停的实体

    public Canvas(double zoom) {
        this.zoom = zoom;
        addMouseWheelListener(this);
        addMouseMotionListener(this);
        addMouseListener(this);
        setAutoscrolls(true);
        setPreferredSize(new Dimension((int)workspace.getWidth(), (int)workspace.getHeight()));
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        //Zoom graphics
        g2d.scale(zoom, zoom);
        //translate graphics to be always in center of the canvas
        Point2D offset = getTranslationOffset();
        g2d.translate(offset.getX(), offset.getY());

        //Draw
        g2d.setColor(Color.LIGHT_GRAY);
        g2d.fill(workspace);
        g2d.setColor(Color.DARK_GRAY);
        g2d.setStroke(new BasicStroke(5.0f));
        g2d.draw(workspace);

        // 绘制实体矩形,根据悬停状态变色
        if (hoveredEntity == entity1) {
            g2d.setColor(Color.RED);
        } else {
            g2d.setColor(Color.DARK_GRAY);
        }
        g2d.draw(entity1);

        if (hoveredEntity == entity2) {
            g2d.setColor(Color.RED);
        } else {
            g2d.setColor(Color.DARK_GRAY);
        }
        g2d.draw(entity2);
    }

    // 新增:获取绘图时的平移偏移
    private Point2D getTranslationOffset() {
        Rectangle size = getBounds();
        double tx = ((size.getWidth() - workspace.getWidth() * zoom) / 2) / zoom;
        double ty = ((size.getHeight() - workspace.getHeight() * zoom) / 2) / zoom;
        return new Point2D.Double(tx, ty);
    }

    @Override
    public void setSize(Dimension size) {
        super.setSize(size);
        if (initialSize == null) {
            this.initialSize = size;
        }
    }

    @Override
    public void setPreferredSize(Dimension preferredSize) {
        super.setPreferredSize(preferredSize);
        if (initialSize == null) {
            this.initialSize = preferredSize;
        }
    }

    public void mouseWheelMoved(MouseWheelEvent e) {
        double zoomFactor = -SCALE_STEP * e.getPreciseWheelRotation() * zoom;
        zoom = Math.abs(zoom + zoomFactor);
        //Here we calculate new size of canvas relative to zoom.
        Dimension d = new Dimension(
                (int)(initialSize.width * zoom),
                (int)(initialSize.height * zoom));
        setPreferredSize(d);
        setSize(d);
        validate();
        followMouseOrCenter(e.getPoint());
        previousZoom = zoom;
    }

    public void followMouseOrCenter(Point2D point) {
        Rectangle size = getBounds();
        Rectangle visibleRect = getVisibleRect();
        scrollX = size.getCenterX();
        scrollY = size.getCenterY();
        if (point != null) {
            scrollX = point.getX() / previousZoom * zoom - (point.getX() - visibleRect.getX());
            scrollY = point.getY() / previousZoom * zoom - (point.getY() - visibleRect.getY());
        }
        visibleRect.setRect(scrollX, scrollY, visibleRect.getWidth(), visibleRect.getHeight());
        scrollRectToVisible(visibleRect);
    }

    public void mouseDragged(MouseEvent e) {
        if (origin != null) {
            int deltaX = origin.x - e.getX();
            int deltaY = origin.y - e.getY();
            Rectangle view = getVisibleRect();
            view.x += deltaX;
            view.y += deltaY;
            scrollRectToVisible(view);
        }
    }

    public void mousePressed(MouseEvent e) {
        origin = new Point(e.getPoint());
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        Point2D offset = getTranslationOffset();
        double originalX = (e.getX() / zoom) - offset.getX();
        double originalY = (e.getY() / zoom) - offset.getY();
        Point2D originalPoint = new Point2D.Double(originalX, originalY);

        if (entity1.contains(originalPoint)) {
            hoveredEntity = entity1;
        } else if (entity2.contains(originalPoint)) {
            hoveredEntity = entity2;
        } else {
            hoveredEntity = null;
        }
        repaint();
    }

    public void mouseClicked(MouseEvent e) {}
    public void mouseReleased(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
}

为什么之前的代码无效?

你之前的坐标计算只处理了缩放,没有正确计算平移偏移:

  • 绘图时的平移是((size.getWidth() - workspace.getWidth() * zoom) / 2) / zoom,而你之前用的是getWidth() - workspace.getWidth() * zoom,既没有除以2(居中的一半偏移),也没有除以zoom(把屏幕空间的偏移转成原始工作区的偏移),所以坐标转换完全错位了。

现在这个方案可以在任何缩放、平移状态下,准确判断鼠标是否在矩形内,并高亮显示红色。

内容的提问来源于stack exchange,提问作者Newbie Guest

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 22:32:55