【发布时间】:2016-04-14 02:41:21
【问题描述】:
我正在尝试使用 swing 和 java2D 创建一个简单的绘图应用程序。 目的是实现平滑缩放,始终相对于鼠标光标点。 该应用程序由两个类组成:CanvasPane 和 Canvas。
- CanvasPane 类是一个简单的容器,中间有 BorderLayout 和 JScrollPane。
- Canvas 类是添加到 CanvasPane 中的 JScrollPane 的绘图组件。 Canvas 绘制一个简单的矩形 [800x600],并调度它的鼠标事件(滚轮和拖动)。
当矩形小于 visibleRect 时,画布大小等于 visibleRect,我调用 AffineTransform.translate 跟随鼠标 (感谢this question)
当矩形变得比画布大时,画布大小也会增长并变得可滚动。然后我在它上面调用 scrollRectToVisible 来跟随鼠标。
问题是: 如何一起使用 translate 和 scrollRectToVisible 来平滑缩放而不会出现图形跳跃。可能有一些已知的决定?
我想要的在YED Graph Editor 中完美实现,但它的代码是封闭的。 我试过很多例子,但只有缩放或滚动,没有复杂的用法。
完整代码如下。
类 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);
}
}
类画布:
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) {
}
}
【问题讨论】:
-
谢谢,但我已经尝试过了。您使用 ZoomWithSelectionInViewport 1) 的示例不使用仿射变换,而是直接绘制计算形状的大小;和 2) 不是鼠标相关的
-
可能类似于this?您需要为缩放添加鼠标支持,但由于所有缩放都在一个地方完成,也许它会提供一些想法
-
谢谢你MadProgrammer,看来我已经有了一个想法(见答案)。
标签: java swing scroll graphics2d zooming