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
相关产品推荐
相关产品推荐

