如何等到定时器完成? (动画使用JAVA)
How to wait until the Timer is finished? (animation using JAVA)
我制作了一个class BarberShopGUI 用于睡眠理发师问题的GUI 动画。动画可以运行完美,而一个物体只需要做一个动画。但是,有一些动画,比如移动到沙发上,移动到椅子上, 移动到收银台并退出。
当调用两个动画方法中的任何一个时
this.moveGuestIn(0); // 0 for the customer id
this.moveGuestToChair(0, 0); // (customer id, nth chair)
动画会同时开始,对象(customer0) 正在摇晃,因为有两个方法在控制它的轴(x, y)。
编辑:根据 Alex 的建议,我现在可以忽略任何进一步的动画请求,方法是使用计时器来标记计时器是否已完成。 (还有一个要检查的 if 语句)但是,我需要将所有动画请求排队而不是忽略它。有什么建议吗?
编辑 2:根据 Maurice Perry 的建议通过代码更新。仍在测试中。
代码如下:
public void moveGuestIn(int n)
{
Point p = new Point(200, 50);
guests.get(n).moveTo(p);
}
-
@Override
public synchronized void moveTo(final Point p)
{
if(timer != null)
return;
timer = new Timer(1000 / 60, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int delta = 0;
if (bounds.x != p.x || bounds.y != p.y) {
delta = Math.abs(bounds.x - p.x);
delta = (delta >= 8) ? 8 : delta;
delta *= ((bounds.x - p.x) < 0) ? 1 : -1;
bounds.x += delta;
delta = Math.abs(bounds.y - p.y);
delta = (delta >= 8) ? 8 : delta;
delta *= ((bounds.y - p.y) < 0) ? 1 : -1;
bounds.y += delta;
repaint();
} else {
timer.stop();
synchronized (Guest.this) {
timer = null;
}
}
}
});
timer.start();
}
全部代码:
BarberShopGUI.java
看来你需要在更高层次上控制你的应用程序状态:你不应该在另一个运动正在进行时开始一个运动。
您现在只需将定时器分配给一个新的定时器实例。在创建新计时器之前尝试检查(以同步方式!)计时器是否为空。 (并且显然在停止后将其设为 null。)
@Override
public void moveTo(final Point p)
{
synchronized (this) {
if(timer != null) {
//ignore requests for new animations while in one
return;
}
timer = new Timer(1000 / 60, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int delta = 0;
if (bounds.x != p.x || bounds.y != p.y) {
delta = Math.abs(bounds.x - p.x);
delta = (delta >= 10) ? 10 : delta;
delta *= ((bounds.x - p.x) < 0) ? 1 : -1;
bounds.x += delta;
delta = Math.abs(bounds.y - p.y);
delta = (delta >= 10) ? 10 : delta;
delta *= ((bounds.y - p.y) < 0) ? 1 : -1;
bounds.y += delta;
repaint();
} else {
timer.stop();
synchronized(Barber.this) {
timer = null;
}
}
}
});
}
timer.start();
}
}
我想我会使用一个动画队列,其中一个动画会实现一些接口,例如这个:
public interface Animation {
public boolean isFinished();
public void nextStep();
}
然后,定时器回调会从队列中一个一个地获取动画,执行它们,并在队列为空时发送通知:
private Queue<Animation> queue = new LinkedList<Animation>();
private Animation getUnfinishedAnimation() {
synchronized(this) {
while (!queue.isEmpty()) {
Animation an = queue.peek();
if (!an.isFinished()) {
return an;
}
queue.poll();
if (queue.isEmpty()) {
notifyAll();
}
}
}
return null;
}
private void nextAnimation() {
synchronized(this) {
queue.poll();
if (queue.isEmpty()) {
notifyAll();
}
}
}
private void timerTic() {
Animation an = getUnfinishedAnimation();
if (an != null) {
an.nextStep();
if (an.isFinished()) {
nextAnimation();
}
}
}
您需要一种方法将动画添加到队列,另一种方法等待队列为空:
public void scheduleAnimation(Animation a) {
synchronized(this) {
queue.add(a);
}
}
public void waitQueueEmpty() throws InterruptedException {
synchronized(this) {
while (!queue.isEmpty()) {
wait();
}
}
}
现在移动客人会变成这样:
public synchronized void moveTo(final Point p) {
scheduleAnimation(new Animation() {
@Override
public boolean isFinished() {
synchronized(Guest.this) {
return bounds.x == p.x && bounds.y == p.y;
}
}
@Override
public void nextStep() {
synchronized(Guest.this) {
int delta = Math.abs(bounds.x - p.x);
delta = (delta >= 10) ? 10 : delta;
delta *= ((bounds.x - p.x) < 0) ? 1 : -1;
bounds.x += delta;
delta = Math.abs(bounds.y - p.y);
delta = (delta >= 10) ? 10 : delta;
delta *= ((bounds.y - p.y) < 0) ? 1 : -1;
bounds.y += delta;
}
repaint();
}
});
}
可以看到只是在调度动画;所以你需要 waitQueueEmpty() 让预定的动画完成。
在我看来,正确的架构如下:
- 动画线程侦听
ActionEvent
秒,并据此更新目标位置。 (或者将它们排队为 waypoints)。
- 当动画完成时(例如带有 "animation finished" 的 ActionEvent),这也是一个事件。其他听众可以使用它来启用按钮等。
- 当单击按钮等时,触发一个事件,让动画对此做出反应,而不是手动启动动画。
模型-视图-控制器的最佳实践是不要将 UI 过多地绑定到您的程序中。事件是实现这种分离的一个很好的抽象。这些部分不是相互交谈,而是都监听事件,并且都可能引发事件。甚至不同的 UI 组件最好通过事件相互交流。
我制作了一个class BarberShopGUI 用于睡眠理发师问题的GUI 动画。动画可以运行完美,而一个物体只需要做一个动画。但是,有一些动画,比如移动到沙发上,移动到椅子上, 移动到收银台并退出。 当调用两个动画方法中的任何一个时
this.moveGuestIn(0); // 0 for the customer id
this.moveGuestToChair(0, 0); // (customer id, nth chair)
动画会同时开始,对象(customer0) 正在摇晃,因为有两个方法在控制它的轴(x, y)。
编辑:根据 Alex 的建议,我现在可以忽略任何进一步的动画请求,方法是使用计时器来标记计时器是否已完成。 (还有一个要检查的 if 语句)但是,我需要将所有动画请求排队而不是忽略它。有什么建议吗?
编辑 2:根据 Maurice Perry 的建议通过代码更新。仍在测试中。
代码如下:
public void moveGuestIn(int n)
{
Point p = new Point(200, 50);
guests.get(n).moveTo(p);
}
-
@Override
public synchronized void moveTo(final Point p)
{
if(timer != null)
return;
timer = new Timer(1000 / 60, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int delta = 0;
if (bounds.x != p.x || bounds.y != p.y) {
delta = Math.abs(bounds.x - p.x);
delta = (delta >= 8) ? 8 : delta;
delta *= ((bounds.x - p.x) < 0) ? 1 : -1;
bounds.x += delta;
delta = Math.abs(bounds.y - p.y);
delta = (delta >= 8) ? 8 : delta;
delta *= ((bounds.y - p.y) < 0) ? 1 : -1;
bounds.y += delta;
repaint();
} else {
timer.stop();
synchronized (Guest.this) {
timer = null;
}
}
}
});
timer.start();
}
全部代码: BarberShopGUI.java
看来你需要在更高层次上控制你的应用程序状态:你不应该在另一个运动正在进行时开始一个运动。
您现在只需将定时器分配给一个新的定时器实例。在创建新计时器之前尝试检查(以同步方式!)计时器是否为空。 (并且显然在停止后将其设为 null。)
@Override
public void moveTo(final Point p)
{
synchronized (this) {
if(timer != null) {
//ignore requests for new animations while in one
return;
}
timer = new Timer(1000 / 60, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int delta = 0;
if (bounds.x != p.x || bounds.y != p.y) {
delta = Math.abs(bounds.x - p.x);
delta = (delta >= 10) ? 10 : delta;
delta *= ((bounds.x - p.x) < 0) ? 1 : -1;
bounds.x += delta;
delta = Math.abs(bounds.y - p.y);
delta = (delta >= 10) ? 10 : delta;
delta *= ((bounds.y - p.y) < 0) ? 1 : -1;
bounds.y += delta;
repaint();
} else {
timer.stop();
synchronized(Barber.this) {
timer = null;
}
}
}
});
}
timer.start();
}
}
我想我会使用一个动画队列,其中一个动画会实现一些接口,例如这个:
public interface Animation {
public boolean isFinished();
public void nextStep();
}
然后,定时器回调会从队列中一个一个地获取动画,执行它们,并在队列为空时发送通知:
private Queue<Animation> queue = new LinkedList<Animation>();
private Animation getUnfinishedAnimation() {
synchronized(this) {
while (!queue.isEmpty()) {
Animation an = queue.peek();
if (!an.isFinished()) {
return an;
}
queue.poll();
if (queue.isEmpty()) {
notifyAll();
}
}
}
return null;
}
private void nextAnimation() {
synchronized(this) {
queue.poll();
if (queue.isEmpty()) {
notifyAll();
}
}
}
private void timerTic() {
Animation an = getUnfinishedAnimation();
if (an != null) {
an.nextStep();
if (an.isFinished()) {
nextAnimation();
}
}
}
您需要一种方法将动画添加到队列,另一种方法等待队列为空:
public void scheduleAnimation(Animation a) {
synchronized(this) {
queue.add(a);
}
}
public void waitQueueEmpty() throws InterruptedException {
synchronized(this) {
while (!queue.isEmpty()) {
wait();
}
}
}
现在移动客人会变成这样:
public synchronized void moveTo(final Point p) {
scheduleAnimation(new Animation() {
@Override
public boolean isFinished() {
synchronized(Guest.this) {
return bounds.x == p.x && bounds.y == p.y;
}
}
@Override
public void nextStep() {
synchronized(Guest.this) {
int delta = Math.abs(bounds.x - p.x);
delta = (delta >= 10) ? 10 : delta;
delta *= ((bounds.x - p.x) < 0) ? 1 : -1;
bounds.x += delta;
delta = Math.abs(bounds.y - p.y);
delta = (delta >= 10) ? 10 : delta;
delta *= ((bounds.y - p.y) < 0) ? 1 : -1;
bounds.y += delta;
}
repaint();
}
});
}
可以看到只是在调度动画;所以你需要 waitQueueEmpty() 让预定的动画完成。
在我看来,正确的架构如下:
- 动画线程侦听
ActionEvent
秒,并据此更新目标位置。 (或者将它们排队为 waypoints)。 - 当动画完成时(例如带有 "animation finished" 的 ActionEvent),这也是一个事件。其他听众可以使用它来启用按钮等。
- 当单击按钮等时,触发一个事件,让动画对此做出反应,而不是手动启动动画。
模型-视图-控制器的最佳实践是不要将 UI 过多地绑定到您的程序中。事件是实现这种分离的一个很好的抽象。这些部分不是相互交谈,而是都监听事件,并且都可能引发事件。甚至不同的 UI 组件最好通过事件相互交流。