0

I need to make an animated movement with my frame and it's inner panels.

While the user clicks on a specific inner panel (a panel which is inside frame) another panel is added to frame's contentPane and then both, frame and new panel grow in width, but I always want my frame in the middle of screen. I solved the animation at this way:

Container container=getContentPane();
int i=0;
ActionListener run = new ActionListener()
{
    @Override
    public void actionPerformed(ActionEvent e)
    {

        frame.setSize(new Dimension(frame.getSize().width + 20, 550));
        frame.setLocationRelativeTo(null);
     //newp is the panel which is just added
        newp.setPreferredSize(new Dimension(newp.getSize().width + 20, 550));
        revalidate();
        repaint();
        container.revalidate();
        container.repaint();
        if (i > 20)
        {
            previousPanel = newp;
            i = 0;
            setInnerPanel(); // add another panel to added panel
            container.revalidate();
            container.repaint();
            timer.stop();
        }
        i++;

    }
};
timer = new Timer(15, run);
timer.setRepeats(true);
timer.start();

now there is an important problem. the animation is NOT smooth. i changed the timer delay to 20 and more but a good a smooth animation didn't come out...how to solve that?

Soheil
  • 1,676
  • 7
  • 34
  • 65
  • what ui toolkit are you using? – Daniel A. White Feb 09 '13 at 14:42
  • `i changed the timer delay to 20 and more` ---- ***Increasing*** the timer delay actually ***decreases*** the FPS. **`FPS=1000/delay`** – Extreme Coders Feb 09 '13 at 15:20
  • @ExtremeCoders what is your suggestion ? – Soheil Feb 09 '13 at 20:11
  • 1
    Make your delay roughly 40 milliseconds (25 frames a second) and decrease the step size (down from 20) and increase the number of integrations. Personally, I would calculate the step size from the number of iterations based on the amount of to you want the animation to play for (for example, if you want the animation to take 1 second, you have 25 iterations, the step size would be roughly targetSzie / 25). From experience, changing the frame location and size tends to be troublesome and needs to be made in values dividable by 2 – MadProgrammer Feb 09 '13 at 20:23
  • @MadProgrammer your suggestions make a smooth animation but not fast enough, i need faster movements, how to make a fast animation? i'm using a VAIO laptop, Core i7, 8 G RAM, while on AC power the animation is smooth and fast otherwise not smooth(the code i mentioned above). what do you mean by " needs to be made in values dividable by 2" ? – Soheil Feb 10 '13 at 07:23
  • 1
    @soheil, from experience, I've found that animated window resizing and repositioning tends to be jittery if you don't use size blues that are divisible by two – MadProgrammer Feb 10 '13 at 07:38
  • Hahaha "blues"...one shouldn't type on one's phone :P – MadProgrammer Feb 10 '13 at 09:41

1 Answers1

2

Quick example, with variable duration...

public class TestAnimatedWindow {

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

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

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

        });
    }

    public class AnimatedPane extends JPanel {

        private GridBagConstraints gbc = new GridBagConstraints();
        private Timer anim;
        private long startTime;
        private int duration = 1000;
        private Rectangle startBounds, targetBounds;
        private JPanel top;
        private JPanel bottom;
        private JSlider durationSlider;
        private JLabel durationLabel;
        private JPanel content;

        public AnimatedPane() {
            top = new JPanel();
            bottom = new JPanel(new GridBagLayout());
            content = new JPanel(new GridBagLayout());
            durationLabel = new JLabel("1000");
            durationSlider = new JSlider(125, 5000);
            durationSlider.setMajorTickSpacing(1000);
            durationSlider.setMinorTickSpacing(500);
            durationSlider.setPaintTicks(true);
            durationSlider.setValue(1000);

            GridBagConstraints gbcBottom = new GridBagConstraints();
            gbcBottom.weightx = 1;
            gbcBottom.fill = GridBagConstraints.HORIZONTAL;
            bottom.add(durationSlider, gbcBottom);
            gbcBottom.weightx = 0;
            gbcBottom.fill = GridBagConstraints.NONE;
            bottom.add(durationLabel, gbcBottom);

            durationSlider.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    int value = durationSlider.getValue();
                    durationLabel.setText(Integer.toString(value));
                }

            });

            setLayout(new BorderLayout());
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            JButton grow = new JButton("Grow");
            top.add(grow, gbc);

            add(top, BorderLayout.NORTH);
            add(content);
            add(bottom, BorderLayout.SOUTH);

            grow.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Stop any animation...
                    anim.stop();
                    // Get the window...
                    Window window = SwingUtilities.windowForComponent(AnimatedPane.this);
                    // What's the windows current bounds...
                    startBounds = window.getBounds();

                    // Add a new component...
                    JPanel panel = new JPanel(new GridBagLayout());
                    panel.add(new JLabel("Boo"));
                    panel.setBorder(new LineBorder(Color.RED));
                    content.add(panel, gbc);

                    // Calculate the bounds we want the window to get to 
                    targetBounds = new Rectangle();
                    // Add in the insets of the windows borders
                    Insets insets = window.getInsets();
                    // Get out preferred size...
                    Dimension size = getPreferredSize();
                    // Add in the border size...
                    size.width += insets.left + insets.right;
                    size.height += insets.top + insets.bottom;
                    targetBounds.setSize(size);

                    // Calculate the viewable position...
                    // Get the viewable screen bounds...
                    Rectangle viewBounds = getScreenViewableBounds();
                    Point p = new Point();
                    // Calculate the center of the screen...
                    p.x = ((viewBounds.width - size.width) / 2) + viewBounds.x;
                    p.y = ((viewBounds.height - size.height) / 2) + viewBounds.y;
                    targetBounds.setLocation(p);

                    // Divisibale by 2, stop the animation jitters
                    if (targetBounds.width % 2 != 0) {
                        targetBounds.width++;
                    }
                    if (targetBounds.height % 2 != 0) {
                        targetBounds.height++;
                    }
                    // Get the time that the animation started...
                    startTime = System.currentTimeMillis();
                    // Get the duration of the animation...
                    duration = durationSlider.getValue();
                    // Start animation...
                    anim.restart();
                }

            });

            // Timer set to about 25 fps...
            anim = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Get the current timer
                    long now = System.currentTimeMillis();
                    // Calculate the difference between now and the start of the animation...
                    long diff = now - startTime;
                    // Get out window...
                    Window window = SwingUtilities.getWindowAncestor(AnimatedPane.this);
                    // The bounds of the window to use
                    Rectangle bounds = targetBounds;
                    // If we've not reached the end of animation cycle...
                    if (diff < duration) {
                        // Calculate the progress of the aniamtion...
                        // That is, the amount of time the animation has been running for
                        // and the duration
                        double progress = (double) diff / (double) duration;
                        // Calculate the bounds over the progress range...
                        bounds = calculateProgress(startBounds, targetBounds, progress);
                        // Divisiable by 2..
                        if (bounds.width % 2 != 0) {
                            bounds.width++;
                        }
                        if (bounds.height % 2 != 0) {
                            bounds.height++;
                        }
                    } else {
                        // Stop the timer..
                        ((Timer) e.getSource()).stop();
                    }
                    // Update the windows bounds
                    window.setBounds(bounds);
                }
            });
            anim.setRepeats(true);
            anim.setCoalesce(true);
            anim.setInitialDelay(0);
        }
    }

    public static Rectangle calculateProgress(Rectangle startBounds, Rectangle targetBounds, double progress) {
        Rectangle bounds = new Rectangle();
        if (startBounds != null && targetBounds != null) {
            bounds.setLocation(calculateProgress(startBounds.getLocation(), targetBounds.getLocation(), progress));
            bounds.setSize(calculateProgress(startBounds.getSize(), targetBounds.getSize(), progress));
        }
        return bounds;
    }

    public static Dimension calculateProgress(Dimension startSize, Dimension targetSize, double progress) {
        Dimension size = new Dimension();
        if (startSize != null && targetSize != null) {
            size.width = calculateProgress(startSize.width, targetSize.width, progress);
            size.height = calculateProgress(startSize.height, targetSize.height, progress);
        }
        return size;
    }

    public static Point calculateProgress(Point startPoint, Point targetPoint, double progress) {
        Point point = new Point();
        if (startPoint != null && targetPoint != null) {
            point.x = calculateProgress(startPoint.x, targetPoint.x, progress);
            point.y = calculateProgress(startPoint.y, targetPoint.y, progress);
        }
        return point;
    }

    public static int calculateProgress(int startValue, int endValue, double fraction) {
        int value = 0;
        int distance = endValue - startValue;
        value = (int) ((float) distance * fraction);
        value += startValue;
        return value;
    }

    public static Rectangle getScreenViewableBounds() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        return getScreenViewableBounds(gd);
    }

    public static Rectangle getScreenViewableBounds(GraphicsDevice gd) {
        Rectangle bounds = new Rectangle(0, 0, 0, 0);
        if (gd != null) {
            GraphicsConfiguration gc = gd.getDefaultConfiguration();
            bounds = gc.getBounds();
            Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);

            bounds.x += insets.left;
            bounds.y += insets.top;
            bounds.width -= (insets.left + insets.right);
            bounds.height -= (insets.top + insets.bottom);
        }
        return bounds;
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • thanks, gave me some ideas. while i want to animate my frame by resizing and repositioning, i don't know why it is jittery, i mentioned what you said (dividable by 2) but still specially while i want to animate `JFrame` it is not smooth...what do you think? – Soheil Feb 10 '13 at 15:49
  • 2
    This depeneds a lot on a lot of different factors. Getting the frame to expand smoothly is probably never going to be entirely possible. At a low duration, I was able to get my example to update over a large area reasonably well (both on Mac and Windows), but at a slow speed, it looked a little "stepped". Growing the frame by divisibles of 2, means you add pixels evenly to each side which should reduce the jittering. Animation is about fooling the eyes into thinking movement has occurred. If you're still having trouble, post you updates (in an update to you question) and I'll have a look) – MadProgrammer Feb 10 '13 at 20:41