如何等到定时器完成? (动画使用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 组件最好通过事件相互交流。