Java2D相对于鼠标位置进行缩放和滚动

时间:2016-01-09 12:41:42

标签: java swing scroll graphics2d zooming

我正在尝试使用swing和java2D创建一个简单的绘图应用程序。 目的是实现平滑的缩放,始终相对于鼠标光标点。 该应用程序由两个类组成:CanvasPane和Canvas。

  • CanvasPane类是一个简单的容器,中间有BorderLayout和JScrollPane。
  • Canvas类是添加到CanvasPane中的JScrollPane的绘图组件。 Canvas绘制一个简单的矩形[800x600],并调度它的鼠标事件(滚轮和拖动)。

当矩形小于visibleRect时,画布大小等于visibleRect,我调用AffineTransform.translate跟随鼠标 (感谢this question

当矩形变大然后画布时,画布大小也会增长并变得可滚动。然后我调用它上面的scrollRectToVisible跟随鼠标。

问题是: 如何将translate和scrollRectToVisible一起使用,以平滑缩放而无需图形跳转。可能有一些已知的决定?

我希望在YED Graph Editor中完全实现,但它的代码已关闭。 我尝试了很多例子,但只有缩放或滚动而没有复杂的使用。

完整代码如下。

Class CanvasPane:

import javax.swing.*;
import java.awt.*;


public class CanvasPane extends JPanel {

    private Canvas canvas;

    public CanvasPane(boolean isDoubleBuffered) {
        super(isDoubleBuffered);
        setLayout(new BorderLayout());
        canvas = new Canvas(1.0);
        JScrollPane pane = new JScrollPane(canvas);
        pane.getViewport().setBackground(Color.DARK_GRAY);
        add(pane, BorderLayout.CENTER);
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Test Graphics");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.add(new CanvasPane(true), BorderLayout.CENTER);
        frame.setSize(new Dimension(1000, 800));
        frame.setVisible(true);
    }
}

Class Canvas:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;


public class Canvas extends JComponent implements MouseWheelListener, MouseMotionListener, MouseListener {
    private double zoom = 1.0;
    public static final double SCALE_STEP = 0.1d;
    private Dimension initialSize;
    private Point origin;
    private double previousZoom = zoom;
    AffineTransform tx = new AffineTransform();
    private double scrollX = 0d;
    private double scrollY = 0d;
    private Rectangle2D rect = new Rectangle2D.Double(0,0, 800, 600);

    public Canvas(double zoom) {
        this.zoom = zoom;
        addMouseWheelListener(this);
        addMouseMotionListener(this);
        addMouseListener(this);
        setAutoscrolls(true);
    }

    public Dimension getInitialSize() {
        return initialSize;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.clearRect(0, 0, getWidth(), getHeight());
        g2d.transform(tx);
        g2d.setColor(Color.DARK_GRAY);
        g2d.fill(rect);
        g2d.setColor(Color.GRAY);
        g2d.setStroke(new BasicStroke(5.0f));
        g2d.draw(rect);
        g2d.dispose();
    }

    @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.
        Rectangle realView = getVisibleRect();
        Dimension d = new Dimension(
                (int)(initialSize.width*zoom),
                (int)(initialSize.height*zoom));
//        if (d.getWidth() >= realView.getWidth() && d.getHeight() >= realView.getHeight()) {
            setPreferredSize(d);
            setSize(d);
            validate();
            followMouseOrCenter(e);
//        }

        //Here we calculate transform for the canvas graphics to scale relative to mouse
            translate(e);
            repaint();
        previousZoom = zoom;
    }

    private void translate(MouseWheelEvent e) {
        Rectangle realView = getVisibleRect();
        Point2D p1 = e.getPoint();
        Point2D p2 = null;
        try {
            p2 = tx.inverseTransform(p1, null);
        } catch (NoninvertibleTransformException ex) {
            ex.printStackTrace();
            return;
        }
        Dimension d = getSize();
        if (d.getWidth() <= realView.getWidth() && d.getHeight() <= realView.getHeight()) {
            //Zooming and translating relative to the mouse position
            tx.setToIdentity();
            tx.translate(p1.getX(), p1.getY());
            tx.scale(zoom, zoom);
            tx.translate(-p2.getX(), -p2.getY());
        } else {
            //Only zooming, translate is not needed because scrollRectToVisible works;
            tx.setToIdentity();
            tx.scale(zoom, zoom);
        }
//        What to do next?
//        The only translation works when rect is smaller then canvas size.
//        Rect bigger then canvas must be scrollable, but relative to mouse position as before.
        // But when the rect gets bigger than canvas, there is a terrible jump of a graphics.
        //So there must be some combination of translation ans scroll to achieve a smooth scale.
        //... brain explosion(((
    }


    public void followMouseOrCenter(MouseWheelEvent e) {
        Point2D point = e.getPoint();
        Rectangle visibleRect = getVisibleRect();

        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();
            Dimension size = getSize();
            view.x += deltaX;
            view.y += deltaY;
            scrollRectToVisible(view);
        }
    }

    public void mouseMoved(MouseEvent e) {
    }

    public void mouseClicked(MouseEvent e) {
    }

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

    public void mouseReleased(MouseEvent e) {

    }

    public void mouseEntered(MouseEvent e) {

    }

    public void mouseExited(MouseEvent e) {

    }

}

1 个答案:

答案 0 :(得分:0)

我终于实现了启蒙=)

我们可以简单地使画布尺寸远大于绘图对象的尺寸,因此忘记计算任何难以理解的变换。

我最初使画布比绘制矩形大100倍。 然后我缩放Graphics2D并在绘画时将缩放的图形转换为画布的中心。接下来,我计算一个新的visibleRect来跟随鼠标点并滚动到它。

当canvas变得不可滚动时,跟随鼠标是不合理的,因为绘图对象太小(比初始尺寸小100倍),所以我只将它居中以便始终可见。 它完全按照我的需要工作。

因此,我们有一个工作示例,使用鼠标缩放鼠标并进行拖动。 代码如下。

CanvasPane类:

import javax.swing.*;
import java.awt.*;


public class CanvasPane extends JPanel {

    private static Canvas canvas;

    public CanvasPane(boolean isDoubleBuffered) {
        super(isDoubleBuffered);
        setLayout(new BorderLayout());
        canvas = new Canvas(1.0);
        JScrollPane pane = new JScrollPane(canvas);
        pane.getViewport().setBackground(Color.DARK_GRAY);
        add(pane, BorderLayout.CENTER);
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Test Graphics");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.add(new CanvasPane(true), BorderLayout.CENTER);
        frame.setSize(new Dimension(1000, 800));
        frame.pack();
        frame.setVisible(true);

        //Initial scrolling of the canvas to its center
        Rectangle rect = canvas.getBounds();
        Rectangle visibleRect = canvas.getVisibleRect();
        double tx = (rect.getWidth() - visibleRect.getWidth())/2;
        double ty = (rect.getHeight() - visibleRect.getHeight())/2;
        visibleRect.setBounds((int)tx, (int)ty, visibleRect.width, visibleRect.height);
        canvas.scrollRectToVisible(visibleRect);
    }
}

Class Canvas:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;


public class Canvas extends JComponent implements MouseWheelListener, MouseMotionListener, MouseListener {
    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 rect = new Rectangle2D.Double(0,0, 800, 600);
    private float hexSize = 3f;

    public Canvas(double zoom) {
        this.zoom = zoom;
        addMouseWheelListener(this);
        addMouseMotionListener(this);
        addMouseListener(this);
        setAutoscrolls(true);

        //Set preferred size to be 100x bigger then drawing object
        //So the canvas will be scrollable until our drawing object gets 100x smaller then its natural size.
        //When the drawing object became so small, it is unnecessary to follow mouse on it,
        //and we only center it on the canvas

        setPreferredSize(new Dimension((int)(rect.getWidth()*100), (int)(rect.getHeight()*100)));
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;

        //Obtain a copy of graphics object without any transforms
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.clearRect(0, 0, getWidth(), getHeight());

        //Zoom graphics
        g2d.scale(zoom, zoom);

        //translate graphics to be always in center of the canvas
        Rectangle size = getBounds();
        double tx = ((size.getWidth() - rect.getWidth() * zoom) / 2) / zoom;
        double ty = ((size.getHeight() - rect.getHeight() * zoom) / 2) / zoom;
        g2d.translate(tx, ty);

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

        //Forget all transforms
        g2d.dispose();
    }

    @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 mouseMoved(MouseEvent e) {
    }

    public void mouseClicked(MouseEvent e) {
    }

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

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

}