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

鼠标位置相对缩放正常,放大时绘制文本越界如何修复?

问题描述

已实现基于鼠标位置的滚轮缩放功能,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);
    }
}

关键修改点

  1. 将缩放时的视图位置调整逻辑从组件绘制层移到鼠标事件处理层,直接操作JViewport.setViewPosition,保证组件绘制坐标始终基于原始原点。
  2. 移除Drawing类中累积的偏移量计算,避免绘制内容被错误平移出边界。
  3. 鼠标滚轮事件中,通过计算缩放前后鼠标点的坐标关系,确保缩放后鼠标位置对应的内容保持在屏幕同一位置,实现平滑的中心缩放。

内容的提问来源于stack exchange,提问作者Some Ace

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 20:15:43