如何在按住按钮时移动 JPanel 并在释放按钮时停止。我尝试过使用带有 Runnable 的 thread.start() 和类似的方法。我总是遇到错误。任何人都可以帮助我吗?
3 回答
您需要考虑许多重要的注意事项。
- 按钮并非设计为以这种方式工作。它们旨在在单击(按下和释放)时触发和操作事件,因此您不能使用正常的操作 API。幸运的是,还有其他方法可以确定按钮的状态。此示例使用 a
ChangeListener
on theButtonModel
并根据模型的状态执行操作。 - 组件通常在布局管理器的控制之下。这意味着为了能够移动组件,我们需要将其关闭(也称为
null
或absolute
布局)。通常,我不鼓励这样做,但这是唯一可行的方法。然而。删除布局管理器后,您将负责确保组件正确定位和调整大小……这不能掉以轻心。更多关于您要实现的目标的背景会产生更好的答案 - 当按钮被“按下”时,我们需要一种方法来确定移动组件的方式。本示例使用一个简单
enum
的方法来确定向哪个方向移动组件。您可以轻松地使用 ax/yDelta
并直接修改组件x/y
位置。两者都应该可以正常工作。 - Swing 是一个单线程环境。也就是说,对 UI 的所有交互和修改都应该在 Event Dispatching Thread 的上下文中执行。但是任何阻止 EDT 的操作都会阻止 UI 开始更新或任何新事件开始处理。这意味着,为了移动组件,我们不能简单地使用 a
while-loop
,因为它永远不会结束(不会处理新事件)。相反,此示例使用 ajavax.swing.Timer
,它在后台等待并在 EDT 上下文中的每个刻度上引发一个ActionEvent
。出现打勾,我们修改面板的位置
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class MovePane {
public static void main(String[] args) {
new MovePane();
}
public MovePane() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public enum Direction {
None, Up, Down, Left, Right;
}
public class TestPane extends JPanel {
private JPanel mobby;
private Timer moveTimer;
private Direction moveDirection = Direction.None;
public TestPane() {
mobby = new JPanel();
mobby.setBackground(Color.RED);
mobby.setSize(50, 50);;
setLayout(new BorderLayout());
JPanel pool = new JPanel(null);
pool.add(mobby);
add(pool);
JPanel buttons = new JPanel(new GridBagLayout());
JButton up = new JButton("Up");
JButton dwn = new JButton("Down");
JButton lft = new JButton("Left");
JButton rgt = new JButton("Right");
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 1;
gbc.gridy = 0;
buttons.add(up, gbc);
gbc.gridx = 1;
gbc.gridy = 2;
buttons.add(dwn, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
buttons.add(lft, gbc);
gbc.gridx = 2;
gbc.gridy = 1;
buttons.add(rgt, gbc);
add(buttons, BorderLayout.SOUTH);
up.getModel().addChangeListener(new ChangeHandler(Direction.Up));
dwn.getModel().addChangeListener(new ChangeHandler(Direction.Down));
lft.getModel().addChangeListener(new ChangeHandler(Direction.Left));
rgt.getModel().addChangeListener(new ChangeHandler(Direction.Right));
moveTimer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Container parent = mobby.getParent();
Rectangle bounds = mobby.getBounds();
switch (moveDirection) {
case Up:
bounds.y--;
break;
case Down:
bounds.y++;
break;
case Left:
bounds.x--;
break;
case Right:
bounds.x++;
break;
}
if (bounds.x < 0) {
bounds.x = 0;
} else if (bounds.x + bounds.width > parent.getWidth()) {
bounds.x = parent.getWidth() - bounds.width;
}
if (bounds.y < 0) {
bounds.y = 0;
} else if (bounds.y + bounds.height > parent.getHeight()) {
bounds.y = parent.getHeight() - bounds.height;
}
mobby.setBounds(bounds);
}
});
moveTimer.setInitialDelay(0);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public class ChangeHandler implements ChangeListener {
private Direction direction;
public ChangeHandler(Direction direction) {
this.direction = direction;
}
@Override
public void stateChanged(ChangeEvent e) {
ButtonModel b = (ButtonModel) e.getSource();
if (b.isPressed()) {
moveDirection = direction;
moveTimer.start();
} else {
moveTimer.stop();
}
}
}
}
}
您可能想阅读Swing 中的并发以了解更多详细信息...
根据 OP 的输入更新
使用击键代替按钮令人惊讶地是相同的方法。你有一个开始动作和一个结束动作,你只需要弄清楚如何应用这些状态。
强烈建议您在KeyListener
. 主要原因是KeyListener
焦点问题,键绑定 API 有能力克服或控制。
基本前提是,您要在按键和按键释放时注册按键动作。使用键绑定 API 相对容易实现这一点。
警告: 此示例一次只允许一个方向。例如,如果您按Up和Down,则向下动作将胜出。这是因为我使用的enum
是方向。xDelta
您可以通过使用 a和值轻松更改此设置yDelta
,这将允许您同时修改垂直和水平方向......但不能为您做所有事情;)
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ButtonModel;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class MovePane {
public static void main(String[] args) {
new MovePane();
}
public MovePane() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public enum Direction {
None, Up, Down, Left, Right;
}
public class TestPane extends JPanel {
private JPanel mobby;
private Timer moveTimer;
private Direction moveDirection = Direction.None;
public TestPane() {
mobby = new JPanel();
mobby.setBackground(Color.RED);
mobby.setSize(50, 50);;
setLayout(new BorderLayout());
JPanel pool = new JPanel(null);
pool.add(mobby);
add(pool);
moveTimer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Container parent = mobby.getParent();
Rectangle bounds = mobby.getBounds();
switch (moveDirection) {
case Up:
bounds.y--;
break;
case Down:
bounds.y++;
break;
case Left:
bounds.x--;
break;
case Right:
bounds.x++;
break;
}
if (bounds.x < 0) {
bounds.x = 0;
} else if (bounds.x + bounds.width > parent.getWidth()) {
bounds.x = parent.getWidth() - bounds.width;
}
if (bounds.y < 0) {
bounds.y = 0;
} else if (bounds.y + bounds.height > parent.getHeight()) {
bounds.y = parent.getHeight() - bounds.height;
}
mobby.setBounds(bounds);
}
});
moveTimer.setInitialDelay(0);
InputMap im = pool.getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = pool.getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "UpPressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "UpReleased");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "DownPressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "DownReleased");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "LeftPressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "LeftReleased");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "RightPressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "RightReleased");
KeyUpAction keyUpAction = new KeyUpAction();
am.put("UpReleased", keyUpAction);
am.put("DownReleased", keyUpAction);
am.put("LeftReleased", keyUpAction);
am.put("RightReleased", keyUpAction);
am.put("UpPressed", new MoveAction(Direction.Up));
am.put("DownPressed", new MoveAction(Direction.Down));
am.put("LeftPressed", new MoveAction(Direction.Left));
am.put("RightPressed", new MoveAction(Direction.Right));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public class KeyUpAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
moveTimer.stop();
moveDirection = Direction.None;
}
}
public class MoveAction extends AbstractAction {
private Direction direction;
public MoveAction(Direction direction) {
this.direction = direction;
}
@Override
public void actionPerformed(ActionEvent e) {
moveDirection = direction;
moveTimer.start();
}
}
}
}
用于SwingTimer
移动面板并添加mouseListener
到面板和覆盖mousePressed
和mouseReleased
方法。
更新:
来自评论:
OP 说我想用
Keyboard buttons
.
您在问题中没有提到键盘,只是说按钮,无论如何,请继续阅读本教程How to use Key Bindings
,它会在现在和将来对您有所帮助。
如果您有问题,请回来,我会发布一个移动面板的示例,但现在,我不会发布它,因为我确定您不会阅读教程,您只需复制我的示例并继续阅读啧啧。
像这样的事情可能会做你想做的事:
有一个包含
private JPanel movingJPanel = new JPanel(); // Declare this however
public void paint(Graphics g) {
//draw background first
Point drawAt;
syncronised (sync) {
drawAt = this.drawAt
}
Dimension size = movingJPanel.getPreferredSize();
Graphics paintWith = g.create(movingJPanel);
movingJPanel.paint(paintWith);
}
private Point moveFrom = new Point(0, 0);
private Point moveTo = new Point(100, 100);
private Point drawAt = new Point(0, 0);
private int steps = 35;
private int step = 0;
private long timeBetweenSteps = 50L;
private Object sync = new Object();
private boolean moving = false;
private Thread thread = new Thread(new Runnable() {
public void run() {
while (!Thread.interrupted()) {
synchronized(sync) {
if (moving && step < steps) {
step++;
drawAt = new Point((moveTo.x - moveFrom.x) * step / steps,
(moveTo.y - moveFrom.y) * step / steps)
drawMovingPanelIn.repaint();
sync.wait(timeBetweenSteps);
}
}
}
}
});
public void start() {
synchronized(sync) {
moving = true;
sync.notify();
}
}
public void start() {
synchronized(sync) {
moving = false;
}
}
public void reset() {
syncronized(sync) {
steps = 0;
}
}
和一个包含
thread.start();
现在从添加到按钮的 MouseListener 的 mousePressed 和 mouseRelaesed 方法调用start()
和方法。stop()