0

I want to realize "smooth movement" when clicking the mouse. Below is my code:

public void mouseClicked(MouseEvent event) {
    int targetX = event.getX();
    int targetY = event.getY();
    for(int i=0;i<10;++i) {
        x = (x+targetX)/2;
        y = (y+targetY)/2;
        repaint();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) { }
    }
}

But the effect is the movement is "direct" from one place to the clicked place. I know threads works in realizing this and the principle is that event dispatching thread should not do much action but may I know why the above code does not work? Can anyone give me some detailed explanation?

class Animation extends Thread {
    /* … */
    public void run() {
        int targetX = event.getX();
        int targetY = event.getY();

        for(int i=0;i<10;++i) {
            panel.x = (panel.x+targetX)/2;
            panel.y = (panel.y+targetY)/2;
            panel.repaint();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
    }
}
HuangJie
  • 1,488
  • 1
  • 16
  • 33
  • Your first example will block the event dispatching thread, preventing the UI from been painted, the `RepaintManager` will consolidate the repaint requests down into as few requests as possible meaning that you end up with a single step from the start to the end. Your second example (potentially) violates the single threaded nature of Swing, but updating the properties of the component outside of the EDT possibly causing dirty paints and other issues – MadProgrammer Jan 19 '17 at 08:49
  • A closer look at [The Event Dispatch Thread](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html), [Painting in AWT and Swing](http://www.oracle.com/technetwork/java/painting-140037.html) and [Performing Custom Painting](http://docs.oracle.com/javase/tutorial/uiswing/painting/) might help shed some more like on the issues – MadProgrammer Jan 19 '17 at 08:56

1 Answers1

2

The Event Dispatching Thread

The EDT does a number of important jobs, apart from processing system events from OS, it also handles the processing of many of the internal events, like painting.

The intention is to provide a single, unified mechanism for dealing with events within a single thread context, reducing the risk of race conditions and deadlocks and undesirable side effects like dirty/partial paints (as an object is been painted, so internal state gets changed which affects what is remaining to be painted)

First Example

public void mouseClicked(MouseEvent event) {
    int targetX = event.getX();
    int targetY = event.getY();
    for(int i=0;i<10;++i) {
        x = (x+targetX)/2;
        y = (y+targetY)/2;
        repaint();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) { }
    }
}

I assume that mouseClicked is an implementation from the MouseListener interface. MouseEvents pass through the EventQueue and are processed within the EDT, meaning that mouseClicked is called within the context of the EDT.

Until the method exists, the EDT is unable to process any further events. This means that the combination of the for-loop and Thread.sleep prevent the EDT from processing events for up to a second.

An important note, repaint does not occur immediately, it posts an paint event into the EventQueue, via the RepaintManager.

One of the side effects the RepaintManager is that it can consolidate repeated repaint requests down into few actual paint events.

So, as a consequence of your for-loop/Thread.sleep, either you are getting all your paint events in a very short time (and you simply can't see them update) or, more likely, you are getting the last one.

Second Example

class Animation extends Thread {
    /* … */
    public void run() {
        int targetX = event.getX();
        int targetY = event.getY();

        for(int i=0;i<10;++i) {
            panel.x = (panel.x+targetX)/2;
            panel.y = (panel.y+targetY)/2;
            panel.repaint();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
    }
}

The second example, while on the surface looks okay, violates the single threaded rules of Swing, that is, any updates to the UI should be made from within the context of the EDT. This is to ensure that any updates don't occur between paint cycles, potentially causing dirty paints or unwanted paint artifacts.

As a general rule, repaint is about the only method I'd consider to be thread safe (there are a couple more, but I keep things simple).

The answer...

Is, complicated, but so is animation. The basic requirements are...

  1. You need to know where you're starting from
  2. You need to know where you're going
  3. You need to know how you're getting there
  4. Updates to the components/UI should be made from within the EDT
  5. Waiting should be done outside the EDT

While you could achieve this using Threads, it becomes considerably more difficult and slightly error prone and requires some more coordination between the threads

You could use a SwingWorker to take some of the guess work out, but most of the time, a simple Swing Timer does a nice job

This example demonstrates, in principle, what you're trying to achieve, it's slightly more complicated, as it attempts to maintain a constent speed no matter the length of the line, but it's an example

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366