Event Listeners and ActionEvents
The javax.swing Timer class generates a sequence of ActionEvents separated by a specified interval.
Why an Animation Seems to Run by Itself
Earlier in the course, timers were used to drive animations. An animation on screen looks smooth, as if something is moving on its own. This article looks at what a timer actually is and what it is doing while the animation appears to run.
Before you read the explanation, keep one question in mind as you think about any animation loop: when the program is waiting between animation frames, which part of the program is doing the waiting? Your own code, or something else? Commit to a guess now. The rest of the article lets you check that guess against the mechanism rather than against memory.
The Repetition Problem Behind Animation
Many animations are the same small job repeated over and over. Change the picture a little, redraw it, wait, and do it again. The hard part is not the change or the redraw. The hard part is making the repetition happen at regular intervals without freezing the rest of the program while it waits.
Before timers were introduced, threads had to be written by hand to achieve similar periodic behavior. A thread is a separate flow of execution, and writing one yourself meant managing its sleeping, waking, and repeating directly. The timer class exists so that this periodic behavior can be requested without writing that machinery each time.
What do you think happens?
When the program is waiting between two animation frames, which part of the program is doing the waiting?
Reveal answer
The background thread inside the Timer does the waiting. Your listener does not run between frames.
A Timer uses a background thread that sleeps most of the time and wakes periodically to generate events. Your listener only performs its work when an event arrives, and the Timer supplies the repetition.
What a Timer Produces
The javax.swing Timer class generates a sequence of ActionEvents separated by a specified interval. Each event is an ActionEvent, and whatever code you attach as the listener receives those events.
The phrase to focus on is sequence. A Timer does not perform your animation itself. Its job is to produce events at a steady rhythm. The animation is what the listener does each time it is called. In that division of labor, the Timer supplies the repetition and your code supplies the content of each repetition.
The constructor takes two things: the delay between events, and the listener that should be told about each event. Creating the timer does not begin the events. Once the timer is created, calling start tells it to begin producing events. Calling start is the moment the rhythm begins.
A Timer has two inputs that matter to you: the delay, which sets the gap between ActionEvents, and the listener, which receives every ActionEvent. Your animation logic belongs in the listener.
Where the Waiting Really Happens
A Timer is not free of threads. Behind the scenes, a Timer uses a thread. That thread spends most of its life asleep. Periodically it wakes up, generates the event associated with the timer, and then goes back to sleep until the next interval. The waiting you were asked to locate in the previous section is therefore done by this background thread.
This is why the Timer is a convenient abstraction. The waiting is handled by a background thread that you do not have to write, and your code only has to describe what should happen on each event. You never write a loop that sleeps and checks the clock yourself.
Read the timeline as the answer to the question posed earlier. The sleeping interval belongs to the Timer's background thread. The listener occupies only the short slice after the wake-up, when one frame of work is done. The rest of the time, the listener is not running at all.
Tracing One Animation Frame
Focus on one cycle of the Timer's background thread. The thread is not continuously running your animation. It checks whether the interval has passed, and if it has, it generates an event. Otherwise it keeps sleeping. The sequence diagram below follows one frame from the wake-up through the listener's work and back to sleep.
- 1The Timer's background thread wakes up because the interval has passed.
- 2The thread generates the
ActionEventassociated with the timer. - 3The event is passed to the listener, which is the code you attached when you created the timer.
- 4The listener performs one frame of work, such as changing a variable and asking the panel to repaint.
- 5When the listener finishes, the thread goes back to sleep until the next interval.
How the ActionEvent Reaches the Listener
Each ActionEvent is an object that the Timer creates and hands to the listener. The listener is the piece of code you supply, so the event travels from the Timer's background thread to your code. The path is short: the Timer generates the event, the event is passed on, and your listener receives it and runs.
Because the listener is called once per event, the number of frames your animation shows is determined by how many events arrive. The Timer decides when events happen, and the listener decides what each event does.
A Sprite Moved by a Timer
The following example uses a sprite whose horizontal position is stored in a variable. The goal is to see exactly which piece of code moves the sprite and which piece makes the change appear on screen. This is an original illustration built on the same pattern described in the source.
int x = 0;
Timer timer = new Timer(16, e -> {
x += 2;
panel.repaint();
});
timer.start();- 1int x = 0; stores the starting horizontal position of the sprite.
- 2new
Timer(16, ...)creates a timer whose events are separated by the delay, and the second argument is the listener that receives eachActionEvent. - 3Inside the listener, x += 2 changes the position; this is the one frame of work done per event.
- 4
panel.repaint()asks the panel to draw again so the new position appears on screen. - 5
timer.start()tells the Timer to begin producing events; nothing moves until this call happens.
- x holds the sprite's horizontal position
- The delay of 16 is the gap between ActionEvents
- The lambda is the listener, called once per event
start()begins the stream of events
On each ActionEvent the listener adds 2 to x and requests a repaint. Successive events therefore move the sprite from 0 to 2, then 4, then 6, and so on, with the Timer's background thread sleeping between each step. The program never runs a loop of its own to wait.Mistakes Beginners Make with Timer Listeners
Common mistake Putting a sleep call inside the listener to control the speed
What it looks likepublic void actionPerformed(ActionEvent e) { x += 2; Thread.sleep(500); panel.repaint(); }Why it failsThe Timer already supplies the waiting between events through its background thread. Adding a sleep inside the listener makes the one piece of code that should be short hold the work up for the sleep time, so the code that is supposed to run once per interval is now blocked.
Do this insteadSet the delay in the Timer constructor and keep the listener to one short frame of work.
Common mistake Doing slow work inside the listener
What it looks likeA listener that loads a file or performs a long calculation before updating the sprite on every event.
Why it failsThe listener is called once per event, so anything slow in it delays the frame it belongs to. The animation then stutters because the frame cannot finish on time.
Do this insteadDo the slow work elsewhere and keep the listener to the small change needed for the next frame.
Common mistake Assuming the listener runs continuously
Why it failsThe listener does not run between events. It runs only when an
ActionEventarrives, which is why one frame of work fits inside each call.Do this insteadThink of the listener as a single step that the Timer calls repeatedly, not as a loop that keeps running.
Common mistake Forgetting to call start
Why it failsThe constructor only receives the delay and listener. Without start, the Timer does not begin producing events, so nothing happens.
Do this insteadAlways call start after creating the Timer.
Common mistake Confusing the delay with the animation's content: the delay sets the gap between ActionEvents, while the listener decides what the animation does.
Before timers were introduced, programmers had to write the periodic thread themselves. The table below compares that hand-written approach with the Timer at the level the source describes.
| Approach | Who writes the waiting | What the programmer supplies |
|---|---|---|
| Hand-written thread | The programmer, who must sleep, wake, and repeat the thread | The thread logic plus the work done each cycle |
| javax.swing Timer | A background thread the Timer provides | The delay and a listener that does one frame of work |
Hand-written threads compared with the javax.swing Timer, following the source's description
Practice: Locate the Waiting
A teammate's Timer listener calls Thread.sleep(1000) before updating a sprite, and the animation looks slow and stuttery while the rest of the window stops responding. Explain which part of the program is supposed to do the waiting, where your teammate has placed the waiting instead, and what change would restore the intended behavior. Use the terms ActionEvent, listener, and background thread in your answer.
Hints
- Ask which component the source says sleeps between events.
- Compare where the listener runs with where the Timer's thread sleeps.
- Think about what the listener should contain according to the one-frame pattern.
Key Takeaways
- The
javax.swingTimer class generates a sequence of ActionEvents separated by a specified interval, and it is created with a delay and a listener and started with start. - The waiting in a Timer-driven animation is done by a background thread that sleeps most of the time and wakes periodically to generate events.
- Each time an
ActionEventarrives, the listener performs one frame of animation, so the listener should stay short. - A sleep or slow work inside the listener blocks the frame it belongs to and undermines the steady rhythm the Timer provides.