Re: Repaint doesn't repaint?
Your animate function is being performed on the event dispatch thread (EDT), and since all drawing/events/etc... is queued onto this thread, no calls will be performed until your function exits (imagine the EDT as the worker which does everything having to do with Swing: repaints, events, etc...but it can only do one thing at a time).
So to animate, you must do so from a separate thread or SwingTimer, which works in parallel to the EDT. For something like this I'd recommend a SwingTimer, as it fires off events at intervals which is great for animation. If you choose to use a Thread, be sure that all calls to Swing changes (eg repaint) be forced onto the EDT using Swing Utilities.
Re: Repaint doesn't repaint?
Copeg, thanks a bunch! I can get the animation working using a swing timer but now I can't seem to understand how to wait for the timer to finish before doing something else to the GUI. I have updated my files to include a reset() method that resets the position of an AnimatedThing after animating it moving to the right. If I call animate() and then reset(), of course the reset happens during the animation. But how do I wait for the timer to finish before calling reset? Sleeping just causes a delay before the animation happens. The timer itself seems to be on the same thread as everything else, so any sleeping or busy waiting I try to do just seems to prevent the timer from firing events. Am I correct? What am I missing?
The main class...
Code :
public class AnimationExample
{
public AnimationExample()
{}
public static void main(String[] args)
{
final AnimationExample example = new AnimationExample();
javax.swing.SwingUtilities.invokeLater(new Runnable()
{public void run(){AnimationViewer.disp();}});
}
}
The viewer class which waits for a mouse click
Code :
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class AnimationViewer extends JPanel implements MouseListener
{
public JLayeredPane pane;
private AnimatedThingsContainer animatedThingsContainer;
public AnimationViewer()
{
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
pane = new JLayeredPane();
pane.setPreferredSize(new Dimension(200, 200));
add(pane);
pane.addMouseListener(this);
AnimatedThing thing = new AnimatedThing();
pane.add(thing);
animatedThingsContainer = new AnimatedThingsContainer(thing);
}
public static void disp()
{
//Create and set up the window.
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
AnimationViewer view = new AnimationViewer();
JScrollPane scrollPane = new JScrollPane(view.pane);
frame.setContentPane(scrollPane);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public void mouseClicked(MouseEvent e)
{
System.out.println("animating...");
animatedThingsContainer.animate();
animatedThingsContainer.reset();
}
public void mouseExited(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
public void mousePressed(MouseEvent e){}
}
The AnimatedThing container
Code :
import java.awt.event.*;
public class AnimatedThingsContainer
{
private AnimatedThing animatedThing;
private int x;
private int y;
private int currentX;
private int currentY;
private int count;
private javax.swing.Timer t;
public AnimatedThingsContainer(AnimatedThing thing)
{
animatedThing = thing;
x = 0;
y = 0;
currentX = x;
currentY = y;
count = 0;
}
public void animate()
{
t = new javax.swing.Timer(10, new ActionListener() {
public void actionPerformed(ActionEvent e) {
animateOneFrame();
}
});
t.start();
}
private void animateOneFrame()
{
count++;
if(count > 50)
{
t.stop();
}
System.out.println("moving one pixel...");
animatedThing.setBounds(currentX, currentY, AnimatedThing.width, AnimatedThing.height);
currentX++;
animatedThing.validate();
animatedThing.repaint();
try{Thread.sleep(50);}catch(Exception e){System.out.println("oh noes");}
}
public void reset()
{
System.out.println("resetting.");
count = 0;
animatedThing.setBounds(x, y, AnimatedThing.width, AnimatedThing.height);
}
}
The AnimatedThing.
Code :
import javax.swing.*;
public class AnimatedThing extends JLabel
{
public static int width = 50;
public static int height = 50;
public AnimatedThing()
{
setIcon(createImageIcon("test.gif"));
setVerticalAlignment(JLabel.BOTTOM);
setOpaque(false);
setBounds(0, 0, width, height);
}
/* createImaceIcon() method taken from The Java Tutorials: How to Use Icons.*/
public static ImageIcon createImageIcon(String path)
{
java.net.URL imgURL = AnimatedThing.class.getResource(path);
if (imgURL != null)
{
return new ImageIcon(imgURL);
}
else
{
System.err.println("Couldn't find file: " + path);
return null;
}
}
}
Thanks in advance!
Re: Repaint doesn't repaint?
Just call it when you stop your timer:
Code :
count++;
if(count > 50)
{
t.stop();
[COLOR="Red"]reset();[/COLOR]
}
Re: Repaint doesn't repaint?
Right, but that's going to make the larger application pretty messy because I might have some loose ends to tie up in the Viewer after calling reset, and then I'll have to rely on my animation calling back to some function in the Viewer. I want to have multiple types of animations so it's not going to be as simple as always calling the same function to finish things up, either. Is there any way I can wait on the timer from within the AnimationViewer? I just tried putting an animationDone flag in the view and having the container call a finish() method that would set the flag to true. But waiting while(!animationDone) prevents the timer from ever firing events, I assume because it's in the same thread and it's waiting for that loop to finish before it can do anything. Of course I can create a finish() method that calls reset(), but that just goes back to my initial objection: in the real code I'm going to have to create a bunch of startAnimationA(), startAnimationB(), finishAnimationA(), finishAnimationB() methods for each type of animation I want, since I don't always wrap up in the same way.
Should I just have the Swing Timer and all of the animation happen in a separate thread and sleep the main thread until the Timer is done? (I still don't quite understand what in the Timer happens in a separate thread; is this redundant somehow?)
Thanks again!
Re: Repaint doesn't repaint?
I'm not fully sure I understand what you're needs are, but assuming I do there are probably dozens of ways to accomplish it...its just how messy/clean/re-useable you want the code. One suggestion would be to set up a listener type system: you register a listener (usually an interface) with an object (stored in some way like an arraylist), and those listeners are fired at the right time. In this case you could just create a new Thread rather than using the Timer. If so you can call sleep in a loop, redrawing for each loop and when the loop reaches the max time exit the loop and fire all your listeners (make sure to make all calls to swing on the EDT). There are also more complex ways that I won't go into but the just is you extend the Runnable interface in such a way as to allow one to know when a thread has finished. Alternatively you could extend Timer, adding the listener code and in your case overriding stop() to run the listeners and then call super.stop().
Re: Repaint doesn't repaint?
After some playing I think I understand my problem a little better. What I want is to start a specific animation in response to a mouse click, wait for the animation to finish, then perform some more wrap-up tasks specific to THAT animation. My frustration comes from the fact that mouse events happen on the EDT, so there is no way for me to wait() for the animation to finish because the current thread will always be the EDT, and suspending the current thread will always suspend the drawing as well.
So for instance, if the user presses a "compress pile of cards" button, I want the View to start the animation of cards sliding together, wait for the animation to finish, then update the Model to reflect that that pile is now compressed.
Something like...
Code :
class View
{
Model model;
Animator animator;
void compressCards()
{
animator.compressCards(); //start the animation of cards being compressed
animator.wait(); //suspend the current thread until animation is finished
disableCompressCardsButton(); //the cards are compressed, remove the button responsible for compressing them
model.compressCards(); //update the model
}
}
I want this all to happen in the space of one function so that I don't have a bunch of startAnimation() and finishAnimation() functions in the View for each unique type of action/animation. (I have about five more planned.) Is there any way to avoid this? I'm avoiding the suggestion of a listener because it just seems messy to me to add so many finishAnimation() functions.
Do I just want the impossible?