拖动鼠标滚动 JScrollPane(Java 摆动)

Scroll JScrollPane by dragging mouse (Java swing)

我正在为我正在开发的游戏制作地图编辑器。 JScrollPane中有一个JPanel,显示要编辑的地图。我想要做的是当用户按住空格键并在 JPanel 中拖动鼠标时,JScrollPanel 将随着拖动一起滚动。这是我目前所拥有的:

panelMapPanel.addMouseMotionListener(new MouseMotionListener(){

        @Override
        public void mouseDragged(MouseEvent e) {
            //Gets difference in distance x and y from last time this listener was called
            int deltaX = mouseX - e.getX();
            int deltaY = mouseY - e.getY();
            mouseX = e.getX();
            mouseY = e.getY();
            if(spacePressed){
                //Scroll the scrollpane according to the distance travelled
                scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getValue() + deltaY);
                scrollPane.getHorizontalScrollBar().setValue(scrollPane.getHorizontalScrollBar().getValue() + deltaX);
            }
        }

});

目前可以使用,但是滚动一点也不流畅。一次移动鼠标很多是可以的,但小的拖动会使滚动窗格变得疯狂。

有什么改进方法吗?

对于那些喜欢视觉的人来说,这里是编辑:

补充说明(编辑):

好吧,结果比我想的要简单得多……

首先,不要乱用 JViewport,而是直接在充当 JScrollPane 内容的组件上使用 JComponent#scrollRectToVisibleMouseListener 应附上。

下面的例子简单地计算了用户点击的点和他们拖动的量之间的差异。然后它将此增量应用于 JViewportviewRect 并使用 JComponent#scrollRectToVisible 更新可视区域,简单:)

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JLabel map;

        public TestPane() {
            setLayout(new BorderLayout());
            try {
                map = new JLabel(new ImageIcon(ImageIO.read(new File("c:/treasuremap.jpg"))));
                map.setAutoscrolls(true);
                add(new JScrollPane(map));

                MouseAdapter ma = new MouseAdapter() {

                    private Point origin;

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

                    @Override
                    public void mouseReleased(MouseEvent e) {
                    }

                    @Override
                    public void mouseDragged(MouseEvent e) {
                        if (origin != null) {
                            JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, map);
                            if (viewPort != null) {
                                int deltaX = origin.x - e.getX();
                                int deltaY = origin.y - e.getY();

                                Rectangle view = viewPort.getViewRect();
                                view.x += deltaX;
                                view.y += deltaY;

                                map.scrollRectToVisible(view);
                            }
                        }
                    }

                };

                map.addMouseListener(ma);
                map.addMouseMotionListener(ma);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

}

我目前正在自己​​开发地图编辑器。尽管这是一个非常冗长的解决方案,但我已经让鼠标滚动顺利地工作了。

我编写了两个自定义 AWTEventListeners,一个用于鼠标事件,另一个用于鼠标移动事件。我这样做是因为我的地图是一个自定义 JComponent,因此不会填满整个视口。这意味着如果光标在组件上,则不会检测到滚动窗格鼠标事件。

对我来说,这工作非常顺利,内容与鼠标光标完美同步滚动。

(我应该提到我使用鼠标滚轮点击而不是 space 栏,但它很容易改变)。

    Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
        public void eventDispatched(AWTEvent event) {
            if(event instanceof MouseEvent){
                MouseEvent e = (MouseEvent)event;
                //Begin a scroll if mouse is clicked on our pane
                if(isMouseInMapPane()){
                    if(e.getID() == MouseEvent.MOUSE_PRESSED){
                        if(e.getButton() == MouseEvent.BUTTON2){
                            mouseWheelDown = true;
                            currentX = MouseInfo.getPointerInfo().getLocation().x;
                            currentY = MouseInfo.getPointerInfo().getLocation().y;
                        }
                    }
                }
                //Stop the scroll if mouse is released ANYWHERE
                if(e.getID() == MouseEvent.MOUSE_RELEASED){
                    if(e.getButton() == MouseEvent.BUTTON2){
                        mouseWheelDown = false;
                    }
                }
            }
        }
    }, AWTEvent.MOUSE_EVENT_MASK);

    Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
        public void eventDispatched(AWTEvent event) {
            if(event instanceof MouseEvent){
                MouseEvent e = (MouseEvent)event;

                //Update the scroll based on delta drag value
                if(e.getID() == MouseEvent.MOUSE_DRAGGED){
                    if(mouseWheelDown){
                        int newX = MouseInfo.getPointerInfo().getLocation().x;
                        int newY = MouseInfo.getPointerInfo().getLocation().y;
                        int scrollStepX = (currentX - newX);
                        int scrollStepY = (currentY - newY);
                        currentX = newX;
                        currentY = newY;

                        //mapScroll is the reference to JScrollPane
                        int originalValX = mapScroll.getHorizontalScrollBar().getValue();
                        mapScroll.getHorizontalScrollBar().setValue(originalValX + scrollStepX);

                        int originalValY = mapScroll.getVerticalScrollBar().getValue();
                        mapScroll.getVerticalScrollBar().setValue(originalValY + scrollStepY);
                    }
                }

            }
        }
    }, AWTEvent.MOUSE_MOTION_EVENT_MASK);

这是 isMouseInPane 方法:

    private boolean isMouseInMapPane(){
    //Note: mapPane does not need to be your scroll pane.
    //it can be an encapsulating container as long as it is in
    //the same position and the same width/height as your scrollPane.
    //For me I used the JPanel containing my scroll pane.
    Rectangle paneBounds = mapPane.getBounds();
    paneBounds.setLocation(mapPane.getLocationOnScreen());
    boolean inside = paneBounds.contains(MouseInfo.getPointerInfo().getLocation());

    return inside;
}

此代码可以放在您有权访问滚动窗格引用的任何位置,或者您可以创建自定义滚动窗格 class 并将其添加到那里。

希望对您有所帮助!

我发现这个(非常常见的)需求出奇地难以解决。这是我们在生产中可能使用了 10 多年的稳定解决方案。

接受的答案似乎很诱人,但一旦开始使用它就会出现可用性故障(例如尝试立即向右下方拖动然后向后拖动,您应该注意到在向后移动期间,没有移动需要放置了很长时间)。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.border.MatteBorder;
import javax.swing.event.MouseInputAdapter;

public class Mover extends MouseInputAdapter {
  public static void main(String[] args) {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(200, 160);
    f.setLocationRelativeTo(null);
    f.setLayout(new BorderLayout());

    JScrollPane scrollPane = new JScrollPane();
    f.add(scrollPane, BorderLayout.CENTER);

    JPanel view = new JPanel();
    view.add(new JLabel("Some text"));
    view.setBorder(new MatteBorder(5, 5, 5, 5, Color.BLUE));
    view.setBackground(Color.WHITE);
    view.setPreferredSize(new Dimension(230, 200));
    new Mover(view);
    scrollPane.setViewportView(view);

    f.setVisible(true);
  }

  private JComponent m_view            = null;
  private Point      m_holdPointOnView = null;

  public Mover(JComponent view) {
    m_view = view;
    m_view.addMouseListener(this);
    m_view.addMouseMotionListener(this);
  }

  @Override
  public void mousePressed(MouseEvent e) {
    m_view.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    m_holdPointOnView = e.getPoint();
  }

  @Override
  public void mouseReleased(MouseEvent e) {
    m_view.setCursor(null);
  }

  @Override
  public void mouseDragged(MouseEvent e) {
    Point dragEventPoint = e.getPoint();
    JViewport viewport = (JViewport) m_view.getParent();
    Point viewPos = viewport.getViewPosition();
    int maxViewPosX = m_view.getWidth() - viewport.getWidth();
    int maxViewPosY = m_view.getHeight() - viewport.getHeight();

    if(m_view.getWidth() > viewport.getWidth()) {
      viewPos.x -= dragEventPoint.x - m_holdPointOnView.x;

      if(viewPos.x < 0) {
        viewPos.x = 0;
        m_holdPointOnView.x = dragEventPoint.x;
      }

      if(viewPos.x > maxViewPosX) {
        viewPos.x = maxViewPosX;
        m_holdPointOnView.x = dragEventPoint.x;
      }
    }

    if(m_view.getHeight() > viewport.getHeight()) {
      viewPos.y -= dragEventPoint.y - m_holdPointOnView.y;

      if(viewPos.y < 0) {
        viewPos.y = 0;
        m_holdPointOnView.y = dragEventPoint.y;
      }

      if(viewPos.y > maxViewPosY) {
        viewPos.y = maxViewPosY;
        m_holdPointOnView.y = dragEventPoint.y;
      }
    }

    viewport.setViewPosition(viewPos);
  }
}