如何等待计时器完成?(使用JAVA制作动画)

如何等待计时器完成?(使用JAVA制作动画),java,multithreading,concurrency,repaint,timertask,Java,Multithreading,Concurrency,Repaint,Timertask,我已经为睡眠理发师问题的GUI动画制作了一个BarberShopGUI类。当一个对象只需要做一个动画时,动画可以很好地运行。但是,有一些动画,如移动到沙发、移动到椅子、移动到收银员和退出。 当调用两个动画方法中的任何一个时 this.moveGuestIn(0); // 0 for the customer id this.moveGuestToChair(0, 0); // (customer id, nth chair) 动画将同时启动,对象(custome

我已经为睡眠理发师问题的GUI动画制作了一个BarberShopGUI类。当一个对象只需要做一个动画时,动画可以很好地运行。但是,有一些动画,如移动到沙发、移动到椅子、移动到收银员和退出。 当调用两个动画方法中的任何一个时

        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);
    }
-

@覆盖
公共同步无效移动到(终点p)
{
如果(计时器!=null)
返回;
计时器=新计时器(1000/60,新ActionListener(){
@凌驾
已执行的公共无效操作(操作事件e){
int delta=0;
if(bounds.x!=p.x | | bounds.y!=p.y){
delta=Math.abs(bounds.x-p.x);
δ=(δ>=8)?8:δ;
delta*=((边界x-p.x)<0)?1:-1;
边界x+=delta;
delta=Math.abs(bounds.y-p.y);
δ=(δ>=8)?8:δ;
delta*=((边界y-p.y)<0)?1:-1;
边界y+=delta;
重新油漆();
}否则{
timer.stop();
已同步(Guest.this){
定时器=空;
}
}
}
});
timer.start();
}
所有代码:

您似乎需要在更高级别上控制应用程序状态:在另一个移动正在进行时,您不应该启动一个移动

您只需立即将计时器分配给新的计时器实例。在创建新计时器之前,请尝试检查(以同步方式!)计时器是否为空。(显然,停止后将其设为空。)

@覆盖
公共无效移动到(终点p)
{
已同步(此){
如果(计时器!=null){
//在一个动画中忽略对新动画的请求
返回;
}
计时器=新计时器(1000/60,新ActionListener(){
@凌驾
已执行的公共无效操作(操作事件e){
int delta=0;
if(bounds.x!=p.x | | bounds.y!=p.y){
delta=Math.abs(bounds.x-p.x);
δ=(δ>=10)?10:δ;
delta*=((边界x-p.x)<0)?1:-1;
边界x+=delta;
delta=Math.abs(bounds.y-p.y);
δ=(δ>=10)?10:δ;
delta*=((边界y-p.y)<0)?1:-1;
边界y+=delta;
重新油漆();
}否则{
timer.stop();
同步(理发师,这个){
定时器=空;
}
}
}
});
}
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();
        }
    });
}
公共同步作废移动到(终点p){
scheduleAnimation(新动画(){
@凌驾
公共布尔值已完成(){
已同步(Guest.this){
返回bounds.x==p.x&&bounds.y==p.y;
}
}
@凌驾
public void nextStep(){
已同步(Guest.this){
int delta=Math.abs(bounds.x-p.x);
δ=(δ>=10)?10:δ;
delta*=((边界x-p.x)<0)?1:-1;
边界x+=delta;
delta=Math.abs(bounds.y-p.y);
δ=(δ>=10)?10:δ;
delta*=((边界y-p.y)<0)?1:-1;
边界y+=delta;
}
重新油漆();
}
});
}

您可以看到,它只是调度动画;因此,您需要waitQueueEmpty()才能完成预定的动画。

在我看来,正确的体系结构如下所示:

  • 动画线程侦听
    ActionEvent
    s,并基于此更新目标位置。(或将其作为航路点排队)
  • 当动画完成时(例如带有“动画完成”的ActionEvent),这也是一个事件。其他监听器可以使用它来启用按钮等
  • 单击按钮等时,触发事件,并让动画对此作出反应,而不是手动启动动画

从模型视图控制器来看,最好的做法是不要将UI太多地绑定到程序中。事件是实现这种分离的良好抽象。这两个部分不是互相交谈,而是倾听事件,并且都可能引发事件。即使是不同的UI组件也最好通过事件相互交流。

感谢您的回复。你能进一步解释一下如何以同步方式进行吗?事实上,很多moveXXToXX方法都会调用moveTo方法,如果在那里做点什么更好的话??在上面的代码中,我使用计时器本身作为
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();
        }
    });
}