2

In this simple code example for an animation of a bouncing ball:

import javax.swing.JApplet;
import javax.swing.JFrame;
import java.awt.*;

public class GraphicsMovement extends JApplet
{
public static void pause()
{
    try {
        Thread.sleep(10);
        } catch(InterruptedException e) {
          }
}

public static void main(String args[])
{
    JApplet example = new GraphicsMovement();
    JFrame frame = new JFrame("Movement");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(example);
    frame.setSize(new Dimension(500,300));       //Sets the dimensions of panel to appear when run
    frame.setVisible(true);
}

  public void paint (Graphics page)
  {
 int width = getWidth();    // width = the width of the panel which appears when run
 int height = getHeight();  // height = the height of the panel which appears when run.

//Changes background color to a blueish color
page.setColor(new Color (140,214,225));
page.fillRect(0,0,width,height);
for(int i = 0; i <= 5; i++)
{
    for (int j = 0; j <= 100; j++)
    {
        page.setColor(Color.YELLOW);
        page.fillOval(100,55 + j,100,100);  //draws a yellow oval
        pause();
        page.setColor(new Color (140,214,225));
        page.fillOval(100,55 + j,100,100);  //draws a blueish oval over the yellow oval
    }
    for (int k = 100; k >= 0; k--)
    {
        page.setColor(Color.YELLOW);
        page.fillOval(100,55 + k,100,100);  //draws a yellow oval
        pause();
        if (k != 0)
        {
            page.setColor(new Color (140,214,225));  //draws a blueish oval over the yellow oval
            page.fillOval(100,55 + k,100,100);
        }
    }
}
 }
 }

The animation is drawn fine and runs on a Windows machine (using JCreator), but will not run on Mac OS X compiled with either IntelliJ or Eclipse. Tried on two different OS X machines, and both will draw the ball and background (after a long wait) but will not proceed with the animation.

Is there some sort of platform-specific code in here that I am missing? Thanks!

mKorbel
  • 109,525
  • 20
  • 134
  • 319
kmypwn
  • 437
  • 1
  • 6
  • 17
  • 2
    _Don't_ sleep on the EDT; _do_ see [*Concurrency in Swing*](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/), in particular, [*Initial Threads*](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html).. – trashgod Oct 22 '12 at 16:40
  • I'm sorry, I'm still new to java... is this because of convention or is the fact that the EDT is sleeping causing these particular issues? I read the links, but I am not sure why threading is important in this specific example. – kmypwn Oct 22 '12 at 17:55
  • 2
    @kmypwn : You don't need `Thread.sleep()`, instead you need to use [javax.swing.Timer](http://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html). Here is one wonderful [example](http://stackoverflow.com/a/9852739/1057230). Threads have nothing to do with your specific example, Threads are related to Swing as a whole, that's why those two links as mentioned by @trashgod. – nIcE cOw Oct 22 '12 at 18:19
  • 2
    Please, please, please call super.paint(g);, the pint methods o a lot of important work in the background you should never ignore – MadProgrammer Oct 22 '12 at 20:19

2 Answers2

3

paint() method is called in the UI thread, and it should return as fast as possible.

So where will you put the animation code then?The answer is simple: you will need to put the code into a separate thread.

For the difference between Windows and OS X, all I can say is that it should be related on how they schedule threads, or something like that.

Hakan Serce
  • 11,198
  • 3
  • 29
  • 48
  • Not sure if the difference is really about thread scheduling, more on how graphics contexts work. In windows, it seems that the updates are propagated immediately do the graphics memory - I can imagine that OS X implicitly uses a double buffer, in which case the whole animation is drawn to the buffer first (the delay), and only the last frame of the animation is flipped from the buffer to video memory. – Kim Sullivan Oct 22 '12 at 15:40
  • That makes sense Kim--Thank you! That could explain the delay for the ball to be drawn on the screen, as the animation is already "done" once the view is displayed. I'll tinker with the code and get it to redraw the screen at each frame. – kmypwn Oct 22 '12 at 16:34
  • So I tried adding many different combinations of validate and repaint statements, but still the animation will not run on its own (sometimes, I can force it by switching windows, but it is still glitchy). Do you know where I should add these statements? – kmypwn Oct 22 '12 at 17:53
  • 1
    It's not about your repaint statements - the paint() method should be written so that it only ever draws a single frame of the animation, so no for-loops that do the animating inside the paint method - you have to keep the "state" (i,j,k in your example) separately, and draw the frame based on the current state. It gets a bit complicated to explain in a few words, have a look at how to write a "game loop" on http://www.java-gaming.org/index.php?topic=24220.0 - you can simplify it some, but the basic idea holds. – Kim Sullivan Oct 23 '12 at 11:48
2

Never do anything in any paint method that would either block or might trigger a repaint request.

You should always call super.paintXxx, these methods do a lot of work in the background, usually a lot better then you can.

You shouldn't need to (very rare cases) extend from top level containers, like JApplet or JFrame. You are better of creating a custom container (such as JPanel) and add your components to it (or perform your custom painting). Apart from the double buffering support, you also gain flexibility in deployment choices.

Don't kid yourself, you don't control the paint process, it's up to the repaint manager to make those decisions, you can however, "encourage" it to update. This causes the most pain when people start playing with animation.

You should modify the "state" of the animation outside of the influence of the Event Dispatching Thread (EDT) or in your case, out side the paint context.

You problem is simple enough that a simple javax.swing.Timer will solve it. More complex animations might require a "animation" thread.

public class GraphicsMovement extends JApplet {

    @Override
    public void init() {
        setLayout(new BorderLayout());
        add(new AnimatedPane());
    }

    @Override
    public void start() {
    }

    public class AnimatedPane extends JPanel {

        private Timer timer;
        private boolean colorSwitch = false;

        private int yOffset = 0;
        private int direction = 1;

        public AnimatedPane() {
            timer = new Timer(10, new ActionListener() {
                public void actionPerformed(ActionEvent e) {
//                    colorSwitch = !colorSwitch;
                    yOffset += direction;
                    if (yOffset > 100) {
                        direction = -1;
                        yOffset = 100;
                    } else if (yOffset < 0){
                        direction = 1;
                        yOffset = 0;
                    }
                    repaint();
                }
            });
            timer.setRepeats(true);
            timer.setCoalesce(true);
            timer.start();

            setBackground(new Color(140, 214, 225));
        }

        @Override
        protected void paintComponent(Graphics page) {
            super.paintComponent(page);
            int width = getWidth();    // width = the width of the panel which appears when run
            int height = getHeight();  // height = the height of the panel which appears when run.

            if (colorSwitch) {
                page.setColor(new Color(140, 214, 225));
            } else {
                page.setColor(Color.YELLOW);
            }
            page.fillOval(100, 55 + yOffset, 100, 100);  //draws a yellow oval
        }
    }
}

I'm concerned with the "delay" of 10 milliseconds, it's enough to want me to face a fit :P

You might find reading through...

Of some interest

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • In your improved example, you forget that the J and K for loops are what's actually doing the animation (in the original, it first draws the oval, then sleeps, then erases the oval using the background color and goes on the next iteration). The J loop is for animating "down", the K loop is for animating "up". – Kim Sullivan Oct 23 '12 at 11:53
  • @kim fair point, didn't have a lot of time under my belt and was having difficulty trying to,figure out all the loops – MadProgrammer Oct 23 '12 at 17:58
  • The code now much better illustrates your point - calculating the offsets and switching movement directions in the thread and just painting at the proper offset whenever you get to actuallly painting. Also, the repaint() in the timer is a nice touch - while it doesn't actually force a repaint, it tells the system that shomething changed and a paint should be done next time the system gets around to do it. – Kim Sullivan Oct 24 '12 at 15:09
  • I really must thank all of you again for your generous help for a java student... I am humbled by how nice and willing you all are in working with me on this. From what I understand, whereas Windows repaints the screen when each object is "drawn", Mac OS X waits for the entire paint() method to be run before it then actually draws what is "left" on the screen (why the j and k loops don't work). The code above does the "animation" through a swing timer and then the paint() method draws one frame per call of the method. One final thing though: – kmypwn Oct 25 '12 at 14:38
  • The improved code, above, compiles (once I added the appropriate import statements), but crashes when running with the following error:Exception in thread "main" java.lang.NoSuchMethodException: GraphicsMovement.main([Ljava.lang.String;) at java.lang.Class.getMethod(Class.java:1605) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:113) As I understand, the @Override on the init method means that the method written above is being called immediately on execution instead of the "stock" one as part of JApplet. Is this what is being considered the "main" method in the error? – kmypwn Oct 25 '12 at 14:38
  • It's an applet, not an application, there is no main method. You need to run in the applet viewer or web browser, or if you're using Netbeans, right click the class file in the projects tab and select run – MadProgrammer Oct 25 '12 at 18:23
  • That did it!!! Thank you all so, so much for your help, I truly don't know how to repay you all :) It warms my heart to know how much the community is willing to help each other out! – kmypwn Oct 25 '12 at 22:32
  • @kmypwn When you're comfortable, pass on what you have learned ;) – MadProgrammer Oct 25 '12 at 22:33