0

Note: I am fairly new to Java so if the answer is incredibly simple, please keep that in mind :)

All I'm trying to do is make a nice looking spiral animation like the one it would show in Windows Media Player while music was playing or like an animation similar to one of the screen savers from Windows XP.

I'm stuck trying to figure out how to create delay between the creation of one line and then the creation of another line.

I want the program to start out with a black screen and every half-second or so, add one line at a slightly different location from the one before making for a cool spiral animation

I'm sure there's a way to do what I want using Thread.Sleep() I just don't know how to do it.

Any help or advice would be greatly appreciated! :D

A picture of my code currently: https://i.stack.imgur.com/fnD32.jpg

2 Answers2

2

Swing is a single threaded environment. Care needs to be taken when you want change the state of the UI on a regular bases. You need to ensure that you don't block the Event Dispatching Thread in any way, doing so will prevent any new paint events (amongst others) from been processed, making your UI look like it's hung and also ensure you that you are synchronising your updates with the Event Dispatching Thread so as to ensure you are not risking any race conditions or other threaded issues

Take a closer look at Concurrency in Swing for more details. A simple approach would be to use a Swing Timer, for example...

enter image description here

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Spinner {

    public static void main(String[] args) {
        new Spinner();
    }

    public Spinner() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new SpinnerPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class SpinnerPane extends JPanel {

        private float angle;

        public SpinnerPane() {
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    angle -= 5;
                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(40, 40);
        }

        protected Point calculateOutterPoint(float angel) {

            int radius = Math.min(getWidth(), getHeight());

            int x = Math.round(radius / 2);
            int y = Math.round(radius / 2);

            double rads = Math.toRadians((angel + 90));

            // This determins the length of tick as calculate from the center of
            // the circle.  The original code from which this derived allowed
            // for a varible length line from the center of the cirlce, we
            // actually want the opposite, so we calculate the outter limit first
            int fullLength = Math.round((radius / 2f)) - 4;

            // Calculate the outter point of the line
            int xPosy = Math.round((float) (x + Math.cos(rads) * fullLength));
            int yPosy = Math.round((float) (y - Math.sin(rads) * fullLength));

            return new Point(xPosy, yPosy);

        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

            int diameter = Math.min(getWidth(), getHeight());

            int x = (getWidth() - diameter) / 2;
            int y = (getHeight() - diameter) / 2;
            Point to = calculateOutterPoint(angle);
            g2d.drawLine(x + (diameter / 2), y + (diameter / 2), x + to.x, y + to.y);
            g2d.dispose();
        }

    }

}

Using similar mechanisms, I've been able to create wait animations like...

enter image description hereenter image description here

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

You can use my AnimationPanel class and do your drawing in it instead. This technique is based on Active Rendering.

Code:

// AnimationPanel.java
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

public abstract class AnimationPanel extends JPanel implements Runnable
{
  private static final long serialVersionUID = 6892533030374996243L;
  private static final int NO_DELAYS_PER_YIELD = 16;
  private static final int MAX_FRAME_SKIPS = 5;

  private static long fps = 30; // Frames Per Second.
  private static long period = 1000000L * (long) 1000.0 / fps;

  protected final int WIDTH;
  protected final int HEIGHT;
  private Thread animator;

  private volatile boolean running = false;
  private volatile boolean isWindowPaused = false;

  private Graphics dbg;
  private Image dbImage = null;

  public AnimationPanel(int width, int height)
  {
    WIDTH = width;
    HEIGHT = height;

    setPreferredSize(new Dimension(WIDTH, HEIGHT));

    setFocusable(true);
    requestFocus();
  }

  public void addNotify()
  {
    super.addNotify();
    startAnimation();
  }

  void startAnimation()
  {
    if (animator == null || !running)
    {
      animator = new Thread(this);
      animator.start();
    }
  }

  public void run()
  {
    long beforeTime, afterTime, timeDiff, sleepTime;
    long overSleepTime = 0L;
    int noDelays = 0;
    long excess = 0L;

    beforeTime = System.nanoTime();

    running = true;

    while (running)
    {
      requestFocus();
      animationUpdate();
      animationRender();
      paintScreen();

      afterTime = System.nanoTime();

      timeDiff = afterTime - beforeTime;
      sleepTime = (period - timeDiff) - overSleepTime;

      if (sleepTime > 0)
      {
        try
        {
          Thread.sleep(sleepTime / 1000000L);
        }
        catch (InterruptedException ignored)
        {
        }

        overSleepTime = (System.nanoTime() - afterTime - sleepTime);
      }
      else
      {
        excess -= sleepTime;
        overSleepTime = 0L;

        if (++noDelays >= NO_DELAYS_PER_YIELD)
        {
          Thread.yield();
          noDelays = 0;
        }
      }

      beforeTime = System.nanoTime();

      int skips = 0;

      while ((excess > period) && (skips < MAX_FRAME_SKIPS))
      {
        excess -= period;
        animationUpdate();
        skips++;
      }
    }

    stopAnimation();
    System.exit(0);
  }

  void stopAnimation()
  {
    running = false;
  }

  private void animationUpdate()
  {
    if (!isWindowPaused)
    {
      update();
    }
  }

  public abstract void update();

  private void animationRender()
  {
    if (dbImage == null)
    {
      dbImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);

      if (dbImage == null)
      {
        System.out.println("Image is null.");
        return;
      }
      else
      {
        dbg = dbImage.getGraphics();
      }
    }

    draw(dbg);
  }

  public abstract void draw(Graphics graphics);

  private void paintScreen()
  {
    Graphics g;

    try
    {
      g = this.getGraphics();
      if ((g != null) && (dbImage != null))
      {
        g.drawImage(dbImage, 0, 0, null);
      }

      Toolkit.getDefaultToolkit().sync();

      if (g != null)
      {
        g.dispose();
      }
    }
    catch (Exception e)
    {
      System.out.println("Graphics context error : " + e);
    }
  }

  public void setWindowPaused(boolean isPaused)
  {
    isWindowPaused = isPaused;
  }
}

How to use:

AnimationPanel is a JPanel. Copy-Paste this class in your project.
Make another class, and make it extend AnimationPanel.
Override the update() and draw() methods of the AnimationPanel which are declared abstract in it.

Inside update() method you can change the values of variables which are declared in this custom class of yours. These variables will be the ones which are required for the animation purposes and which keep changing from frame to frame.

Inside draw() method, do all your custom painting using the variables that you've defined in your custom class.

You can use setWindowPaused() method to pause the animation if you like to as well.

Demonstration:

// DemonstrationPanel.java
import java.awt.*;

public class DemonstrationPanel extends AnimationPanel
{
  private int red, green, blue;
  private int a, b, c, d;

  public DemonstrationPanel(int WIDTH, int HEIGHT)
  {
    super(WIDTH, HEIGHT);

    red = 100;
    green = blue = 5;

    a = 2;
    b = 500;
    c = 200;
    d = 5;
  }

  @Override
  public void update()
  {
    red += 5;
    red %= 255;

    blue += 1;
    blue %= 255;

    green += 10;
    green %= 255;

    a += 20;
    a %= HEIGHT;

    b += 1;
    b %= WIDTH;

    c += 15;
    c %= HEIGHT;

    d += 20;
    d %= WIDTH;
  }

  @Override
  public void draw(Graphics graphics)
  {
    // Uncomment the below two statements to just see
    // one line per frame of animation:

    // graphics.setColor(BACKGROUND_COLOR);
    // graphics.fillRect(0, 0, WIDTH, HEIGHT);

    graphics.setColor(new Color(red, green, blue));
    graphics.drawLine(b, c, d, a);
  }
}

And here's Demo class:

// Demo.java
import javax.swing.*;
import java.awt.*;

public class Demo
{
  public Demo()
  {
    JFrame frame = new JFrame("Demo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(new DemonstrationPanel(800, 600));
    frame.setBackground(Color.BLACK);
    frame.setSize(800, 600);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  public static void main(String[] args)
  {
    SwingUtilities.invokeLater(new Runnable()
    {
      @Override
      public void run()
      {
        new Demo();
      }
    });
  }
}

You can adjust the value of fps in the AnimationPanel class to change the speed of the animation.

Aman Agnihotri
  • 2,973
  • 1
  • 18
  • 22
  • GetGraphics is not how custom painting is done; this likely violating the single thread rules of Swing; you should not dispose of a Graphics context you did not create, this can prevent what was painted from been painted to the screen on some systems; this could just as easily be accomplished with a Swing Timer or even better, a animation library like Trident or TiminggFramework – MadProgrammer Apr 05 '14 at 06:56
  • @MadProgrammer: Check out as to whose graphics I'm getting. Its an image's graphics that I am getting and painting on it, making use of the double buffering technique. In this program I have taken control of the whole painting by using my own rendering thread. There are other ways of doing the same thing, but this one's a very good approach as well. You can test the program in your IDE if you want to see this technique in action. – Aman Agnihotri Apr 05 '14 at 07:00
  • g = this.getGraphics(); is clearly the components graphics context, the you drawing the image to it, outside of the EDT...you don't control the paint process. At any time, repaint manager could over paint what you've done, causing flickering. Swing components are already double buffered. As it stands, it's a great example of not what to do – MadProgrammer Apr 05 '14 at 07:03
  • @MadProgrammer: Well Swing Timer is not as fast as this method. And the use of `getGraphics()` is not recommended for Swing based components, but I am getting the graphics of an `Image`. That's a totally different thing. And this code is independent of any third party library. – Aman Agnihotri Apr 05 '14 at 07:04
  • 1- this.getGraphics is getting the components graphics context, outside of the EDT. That's plain as day. This method can return null and the contents can be overridden by the repaint manager at any time by the repaint manager. 2- The only additional overhead of Swing Timer is the synchronisation of the action event to the EDT. I bet with the inclusion of a InvokeLater, it would come close to be the same as a Swing Timer. You can this by placing a call to repaint before you're Thread.sleep call – MadProgrammer Apr 05 '14 at 07:08
  • this.getGraphics is been called in paintScreen(), it is how you are able to render the image to screen otherwise nothing would be painted! – MadProgrammer Apr 05 '14 at 07:10
  • @MadProgrammer: Well if you look just one line down that line, you'll see that I have ensured that if the graphics is null, it'll do its work fine. – Aman Agnihotri Apr 05 '14 at 07:16
  • @MadProgrammer: Did you try this program on your system? – Aman Agnihotri Apr 05 '14 at 07:16
  • *"as it would have crashed had it violated any Swing rules"* No, you'll end up with a race condition as two threads compete to update the screen at the same time, producing dirty paints... – MadProgrammer Apr 05 '14 at 07:16
  • What does `paintComponent` do...it fills the graphics context with the background color of the component...just because you didn't override it doesn't mean it's not called... – MadProgrammer Apr 05 '14 at 07:21
  • @MadProgrammer: Alright. There's a difference in some opinions here. :) It'll get called, but not many times, unlike the custom thread based animation that I'm doing. The frame per second is set to 30, which will make any anomalies go away, if it ever happens, and the human eye won't be able to notice it. The animator thread is a dedicated thread for painting on this custom component and its getting updated frequently unlike the sparsely updated `paintComponent()` method of the Swing component. – Aman Agnihotri Apr 05 '14 at 07:27
  • I added a call to `repaint` before the `Thread.sleep` to simulate and got [this](http://imgur.com/JRfW9n4) result. It wouldn't take much to throw it off in production and have you scratching your head wondering why... – MadProgrammer Apr 05 '14 at 07:38
  • @MadProgrammer: Well actually I can give a whole lecture on as to why that `repaint()` before `Thread.sleep` led to bad behavior of the program. But I know you know why so I won't. `getGrpahics()` isn't evil if used properly. What my program does is called `Active Rendering`. Here's a link to that from Oracle's Java tutorials: [Active Rendering](http://docs.oracle.com/javase/tutorial/extra/fullscreen/rendering.html). – Aman Agnihotri Apr 05 '14 at 07:45
  • There is a difference between how active rendering is actually achieved through AWT and what you are doing. You are trying simulate active rendering on-top of an API that is passively driven which results in any number of problems. Generally speaking, `getGraphics` is discouraged for these very reasons. There is away that custom painting has been designed into the system, failing to utilise it is simply asking for trouble – MadProgrammer Apr 05 '14 at 07:48
  • @MadProgrammer: This technique I used is from my game loops, where the games screen had the same size throughout the game. Its performance-wise efficient, and fulfills a particular requirement. If one has similar requirements, one can use this method. I'm not the first one to employ this technique. – Aman Agnihotri Apr 05 '14 at 07:58
  • Active rendering is typically achieved through the use of `BufferStrategy` which comes (typically) from the AWT `Canvas`, there are other elements which need to be taken into account as well, but typically speaking makes it incompatible with Swing programs (at least incredibly difficult) - The problem is, you are still mixing to paint engines which are competing within the some context... – MadProgrammer Apr 05 '14 at 08:02