我正在尝试在JScrollPane中编码可缩放图像.
当图像完全缩小时,它应该水平和垂直居中.当两个滚动条都出现时,变焦应该总是相对于鼠标坐标发生,即图像的相同点应该在缩放事件之前和之后的鼠标下面.
我几乎实现了我的目标.不幸的是,“scrollPane.getViewport().setViewPosition()”方法有时无法正确更新视图位置.在大多数情况下,调用该方法两次(黑客!)克服了这个问题,但视图仍然闪烁.
我没有解释为什么会这样.但是我相信这不是数学问题.
以下是MWE.要查看我的问题,您可以执行以下操作:
>放大直到你有一些滚动条(200%变焦左右)
>单击滚动条滚动到右下角
>将鼠标放在角落并放大两次.第二次你会看到滚动位置如何向中心跳跃.
如果有人能告诉我问题所在,我真的很感激.谢谢!
package com.vitco; import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseWheelEvent; import java.awt.image.BufferedImage; import java.util.Random; /** * Zoom-able scroll panel test case */ public class ZoomScrollPanel { // the size of our image private final static int IMAGE_SIZE = 600; // create an image to display private BufferedImage getImage() { BufferedImage image = new BufferedImage(IMAGE_SIZE,IMAGE_SIZE,BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); // draw the small pixel first Random rand = new Random(); for (int x = 0; x < IMAGE_SIZE; x += 10) { for (int y = 0; y < IMAGE_SIZE; y += 10) { g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255))); g.fillRect(x,y,10,10); } } // draw the larger transparent pixel second for (int x = 0; x < IMAGE_SIZE; x += 100) { for (int y = 0; y < IMAGE_SIZE; y += 100) { g.setColor(new Color(rand.nextInt(255),180)); g.fillRect(x,100,100); } } return image; } // the image panel that resizes according to zoom level private class ImagePanel extends JPanel { private final BufferedImage image = getImage(); @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g.create(); g2.scale(scale,scale); g2.drawImage(image,null); g2.dispose(); } @Override public Dimension getPreferredSize() { return new Dimension((int)Math.round(IMAGE_SIZE * scale),(int)Math.round(IMAGE_SIZE * scale)); } } // the current zoom level (100 means the image is shown in original size) private double zoom = 100; // the current scale (scale = zoom/100) private double scale = 1; // the last seen scale private double lastScale = 1; public void alignViewPort(Point mousePosition) { // if the scale didn't change there is nothing we should do if (scale != lastScale) { // compute the factor by that the image zoom has changed double scaleChange = scale / lastScale; // compute the scaled mouse position Point scaledMousePosition = new Point( (int)Math.round(mousePosition.x * scaleChange),(int)Math.round(mousePosition.y * scaleChange) ); // retrieve the current viewport position Point viewportPosition = scrollPane.getViewport().getViewPosition(); // compute the new viewport position Point newViewportPosition = new Point( viewportPosition.x + scaledMousePosition.x - mousePosition.x,viewportPosition.y + scaledMousePosition.y - mousePosition.y ); // update the viewport position // IMPORTANT: This call doesn't always update the viewport position. If the call is made twice // it works correctly. However the screen still "flickers". scrollPane.getViewport().setViewPosition(newViewportPosition); // debug if (!newViewportPosition.equals(scrollPane.getViewport().getViewPosition())) { System.out.println("Error: " + newViewportPosition + " != " + scrollPane.getViewport().getViewPosition()); } // remember the last scale lastScale = scale; } } // reference to the scroll pane container private final JScrollPane scrollPane; // constructor public ZoomScrollPanel() { // initialize the frame JFrame frame = new JFrame(); frame.setDefaultCloSEOperation(WindowConstants.EXIT_ON_CLOSE); frame.setSize(600,600); // initialize the components final ImagePanel imagePanel = new ImagePanel(); final JPanel centerPanel = new JPanel(); centerPanel.setLayout(new GridBagLayout()); centerPanel.add(imagePanel); scrollPane = new JScrollPane(centerPanel); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); frame.add(scrollPane); // add mouse wheel listener imagePanel.addMouseWheelListener(new MouseAdapter() { @Override public void mouseWheelMoved(MouseWheelEvent e) { super.mouseWheelMoved(e); // check the rotation of the mousewheel int rotation = e.getWheelRotation(); boolean zoomed = false; if (rotation > 0) { // only zoom out until no scrollbars are visible if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() || scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) { zoom = zoom / 1.3; zoomed = true; } } else { // zoom in until maximum zoom size is reached double newCurrentZoom = zoom * 1.3; if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom zoom = newCurrentZoom; zoomed = true; } } // check if a zoom happened if (zoomed) { // compute the scale scale = (float) (zoom / 100f); // align our viewport alignViewPort(e.getPoint()); // invalidate and repaint to update components imagePanel.revalidate(); scrollPane.repaint(); } } }); // display our frame frame.setVisible(true); } // the main method public static void main(String[] args) { new ZoomScrollPanel(); } }
注意:我也在这里查看了JScrollPane setViewPosition After “Zoom”的问题,但不幸的是问题和解决方案略有不同,不适用.
编辑
我已经通过使用hack解决了这个问题,但是我仍然没有更接近理解底层问题是什么.发生的事情是,当调用setViewPosition时,某些内部状态更改会触发对setViewPosition的其他调用.这些额外的呼叫偶尔会发生.当我阻止它们时,一切都很完美.
为了解决这个问题,我简单介绍了一个新的布尔变量“blocked = false;”并替换了线
scrollPane = new JScrollPane(centerPanel);
和
scrollPane.getViewport().setViewPosition(newViewportPosition);
同
scrollPane = new JScrollPane(); scrollPane.setViewport(new JViewport() { private boolean inCall = false; @Override public void setViewPosition(Point pos) { if (!inCall || !blocked) { inCall = true; super.setViewPosition(pos); inCall = false; } } }); scrollPane.getViewport().add(centerPanel);
和
blocked = true; scrollPane.getViewport().setViewPosition(newViewportPosition); blocked = false;
如果有人能理解这一点,我仍然会非常感激!
解决方法
这是完整的,功能齐全的代码.我仍然不明白为什么黑客是必要的,但至少它现在按预期工作:
import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseWheelEvent; import java.awt.image.BufferedImage; import java.util.Random; /** * Zoom-able scroll panel */ public class ZoomScrollPanel { // the size of our image private final static int IMAGE_SIZE = 600; // create an image to display private BufferedImage getImage() { BufferedImage image = new BufferedImage(IMAGE_SIZE,(int)Math.round(IMAGE_SIZE * scale)); } } // the current zoom level (100 means the image is shown in original size) private double zoom = 100; // the current scale (scale = zoom/100) private double scale = 1; // the last seen scale private double lastScale = 1; // true if currently executing setViewPosition private boolean blocked = false; public void alignViewPort(Point mousePosition) { // if the scale didn't change there is nothing we should do if (scale != lastScale) { // compute the factor by that the image zoom has changed double scaleChange = scale / lastScale; // compute the scaled mouse position Point scaledMousePosition = new Point( (int)Math.round(mousePosition.x * scaleChange),viewportPosition.y + scaledMousePosition.y - mousePosition.y ); // update the viewport position blocked = true; scrollPane.getViewport().setViewPosition(newViewportPosition); blocked = false; // remember the last scale lastScale = scale; } } // reference to the scroll pane container private final JScrollPane scrollPane; // constructor public ZoomScrollPanel() { // initialize the frame JFrame frame = new JFrame(); frame.setDefaultCloSEOperation(WindowConstants.EXIT_ON_CLOSE); frame.setSize(600,600); // initialize the components final ImagePanel imagePanel = new ImagePanel(); final JPanel centerPanel = new JPanel(); centerPanel.setLayout(new GridBagLayout()); centerPanel.add(imagePanel); scrollPane = new JScrollPane(); scrollPane.setViewport(new JViewport() { private boolean inCall = false; @Override public void setViewPosition(Point pos) { if (!inCall || !blocked) { inCall = true; super.setViewPosition(pos); inCall = false; } } }); scrollPane.getViewport().add(centerPanel); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); frame.add(scrollPane); // add mouse wheel listener imagePanel.addMouseWheelListener(new MouseAdapter() { @Override public void mouseWheelMoved(MouseWheelEvent e) { super.mouseWheelMoved(e); // check the rotation of the mousewheel int rotation = e.getWheelRotation(); boolean zoomed = false; if (rotation > 0) { // only zoom out until no scrollbars are visible if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() || scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) { zoom = zoom / 1.3; zoomed = true; } } else { // zoom in until maximum zoom size is reached double newCurrentZoom = zoom * 1.3; if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom zoom = newCurrentZoom; zoomed = true; } } // check if a zoom happened if (zoomed) { // compute the scale scale = (float) (zoom / 100f); // align our viewport alignViewPort(e.getPoint()); // invalidate and repaint to update components imagePanel.revalidate(); scrollPane.repaint(); } } }); // display our frame frame.setVisible(true); } // the main method public static void main(String[] args) { new ZoomScrollPanel(); } }