-1

I am trying to make a simple animation in which a rectangle starts off the screen (on the right side of the right edge of the screen) and moves towards the left. So, in this case, my frame has a width of 1000 and the wall starts off at an x-value of 1100. Obviously, at first, the rectangle is not supposed to be visible to us. But as the rectangle moves to the left, it should eventually become visible. However, this animation doesn't do that. Even when the wall's x-value is within the bounds of the screen, it isn't being rendered.

I tried putting a println() statement in the paintComponent() method of the wall, and I discovered that the paintComponent() wasn't getting called by the frame's repaint() method. I figured that when the wall was first added to the frame, Swing decided that since it was off the screen it doesn't need to get rendered, and so even when the wall eventually gets on the screen Swing thinks that it doesn't need to get rendered.

I have tried revalidating and invalidating the frame and the component, but nothing has worked. Please help me to fix this. Below is the code:

package graphics.simpleAnimation;

public class Simple_Animation implements Runnable {

    private UI ui; // The UI (frame)

    private Wall wall; // Wall object that moves across the screen

    private Simple_Animation() {

        // Initialize the wall object (intentionally given an x value that is greater than the frame's width)
        wall = new Wall(1100, 400, 200, 400);

        // Initialize the UI (width is only 1000)
        ui = new UI(1000, 600, "Geometry Dash");

        // Add the wall to the ui (the frame)
        ui.add(wall);

    }

    public void run() {
        // Set the frame visible
        ui.setVisible(true);

        // Repaint the frame and move the wall
        while (true) {
            ui.repaint();
            wall.moveWall(-2, 0);

            try {
                Thread.sleep(16);
            } catch (InterruptedException IE) {
                System.out.println(IE);
            }

        }

    }

    // Starts the program in a new thread
    public static void main(String[] args) {
        Simple_Animation simpleAnimation = new Simple_Animation();
        new Thread(simpleAnimation).start();
    }

}


package graphics.simpleAnimation;

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

public class UI extends JFrame {

    // Variables storing the width and height of the content pane (where the components are being rendered)
    public int content_pane_width;
    public int content_pane_height;

    public UI(int frameW, int frameH, String frameTitle) {

        setTitle(frameTitle);
        setSize(frameW, frameH);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLayout(null);

        content_pane_width = getContentPane().getWidth();
        content_pane_height = getContentPane().getHeight();

    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
    }

}

package graphics.simpleAnimation;

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

public class Wall extends JComponent {

    private int wallX;
    private int wallY;
    private int wallW;
    private int wallH;


    Wall(int x, int y, int sizeX, int sizeY) {
        wallX = x;
        wallY = y;
        wallW = sizeX;
        wallH = sizeY;

        setSize(getPreferredSize());
    }

    public void moveWall(int moveX, int moveY) {
        wallX += moveX;
        wallY += moveY;
    }

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

    @Override
    public void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;

        setLocation(wallX, wallY);
        g2d.fillRect(0, 0, wallW, wallH);
    }
}
Cache Staheli
  • 3,510
  • 7
  • 32
  • 51
  • 1
    Don't do `setLocation(wallX, wallY);` within a paint method, painting should paint the current state never change it. You're also mixing your own properties with the components properties, I'd suggest simply using the components pre-existing properties, since you're trying to place/size the component yourself. Also, `getPreferredSize` won't do anything when you're using a `null` layout, which probably explains part of your problem – MadProgrammer Feb 22 '17 at 23:40
  • See [Detection/fix for the hanging close bracket of a code block](http://meta.stackexchange.com/q/251795/155831) for a problem I could no longer be bothered fixing. – Andrew Thompson Feb 23 '17 at 00:27

2 Answers2

3

There are a couple of errors I can find in your program

  1. You're using a null layout, please see Null layout is evil and the answers in this question to see why you should avoid its use. (Probably not in this case, as per @MadProgrammer's comment below), this is just another approach

  2. while (true) { this line might block the Event Dispatch Thread (EDT) along with this line: Thread.sleep(16);, See Lesson: Concurrency in Swing to learn more about and How to use Swing Timers. You should also place your program on the EDT which can be done:

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                //Your constructor here
            }
        });
    }
    
  3. You're not calling super.paintComponent() on your paintComponent() method of the Wall class which could break the paint chain, always call it first.

  4. You're extending JComponent, it would be better to extend JPanel and do custom painting over it using the Shapes API

With all of the above in mind, you can then have a code like this one:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class SingleAnimation {

    private JFrame frame;
    private Timer timer;

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

    public void createAndShowGui() {
        frame = new JFrame(getClass().getSimpleName());

        Wall wall = new Wall(300, 0);

        timer = new Timer(16, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                wall.moveWall(-2, 0);
            }
        });

        timer.setInitialDelay(0);
        timer.start();

        frame.add(wall);
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

class Wall extends JPanel {
    private int xCoord;
    private int yCoord;

    public int getxCoord() {
        return xCoord;
    }

    public void setxCoord(int xCoord) {
        this.xCoord = xCoord;
    }

    public int getyCoord() {
        return yCoord;
    }

    public void setyCoord(int yCoord) {
        this.yCoord = yCoord;
    }

    public Wall(int x, int y) {
        this.xCoord = x;
        this.yCoord = y;
    }

    public void moveWall(int xUnits, int yUnits) {
        xCoord += xUnits;
        yCoord += yUnits;
        repaint();
    }

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

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.BLUE);

        g2d.fill(new Rectangle2D.Double(xCoord, yCoord, 100, 20));
    }
}

Which will produce a similar output like this one:

enter image description here

Community
  • 1
  • 1
Frakcool
  • 10,915
  • 9
  • 50
  • 89
  • No argument with your example, but I'd need more context to the question to be able to decide which approach is correct *"You're using a null layout, please see Null layout is evil and the answers in this question to see why you should avoid its use."* is an oversimplification of the issue and 99% of the time when you think you need a `null` layout, you don't, but I can think of a number of times when it's useful (like performing component based animation), `null` layouts are a tool which they can be when used right, but most people use them in the wrong way and at the wrong time ;) – MadProgrammer Feb 23 '17 at 01:15
  • @MadProgrammer thanks for your feedback, I already saw your example, it works great, I learned something new today thanks to your comment. I need to look at more examples where `null layout` is useful, (not for placing components in a GUI but more like this one) :) – Frakcool Feb 23 '17 at 01:19
  • Don't get me wrong, I agree, `null` layouts are generally a bad idea in the wrong hands, it's like fire, it's useful, but if you're not careful, you'll burn the house down ;) - Nice example by the way ;) – MadProgrammer Feb 23 '17 at 01:21
  • 1
    @MadProgrammer thanks for that :) I like Swing and it's the first time I see those "exceptions" where a `null layout` might be a nice idea. I don't want to burn my house, just light it a little more. btw see the edit on the answer :) – Frakcool Feb 23 '17 at 01:24
  • Hi guys, thanks a lot for the answer. Just wanted to ask if you could elaborate more on why I should extend JPanel but not JComponent. Also I've seen people extending Canvas for custom rendering- how is that approach? – Sukhveer Sahota Feb 23 '17 at 03:04
  • @SukhveerSahota `JComponent` is abstract, so you can't instantiate it, also it doesn't call `setOpaque(true)` whereas `JPanel` does. `Canvas` is an AWT Component, which is outdated and no longer supported, `JPanel` is the Swing "evolution" of `Canvas` which provides more functionallity... – Frakcool Feb 23 '17 at 03:21
  • ... however I think @MadProgrammer can give you a way much better explanation, and [this](http://stackoverflow.com/questions/20787800/what-are-the-benefits-to-painting-on-a-jpanel-vs-jcomponent) could be related – Frakcool Feb 23 '17 at 03:25
  • 1
    @SukhveerSahota The only benefit of using a Canvas is for its BufferStrategy support, which hands complete control and responsibility for its rendering to you, but mixing awt and swing is never a good idea. As stated, JComponent is transparent by default, thus might be a good thing, but if all you're going to do is fill it anyway, it's a inefficient. Generally I start from JPanel mostly from habit and simplicity – MadProgrammer Feb 23 '17 at 03:29
  • Ok so when I first started using Swing, I used JPanel for my rendering. But then I ran into the problem of rendering multiple JPanels on the frame, as only one of them was being rendered. That's why I switched to JComponent. Is there a way of using JPanel in that scenario (and does it involve layout managers?) or will I have to use JComponent in that case? – Sukhveer Sahota Feb 23 '17 at 14:37
  • @SukhveerSahota It depends on how you want your GUI to look like, you can have multiple JPanels, one inside another, each with a different layout which will allow you to have your desired GUI, or sometimes just one JPanel can give you the desired output, but as I said, we need to see the expected GUI to know which layout to use – Frakcool Feb 23 '17 at 14:40
  • My desired effect is to have multiple walls starting off the screen and slowly moving to the left to make it look like the background is moving to the right. How would that work with JPanels? Also, if I decide to stick to JComponent, would I be able to achieve this functionality? – Sukhveer Sahota Feb 23 '17 at 14:58
  • @SukhveerSahota Just have multiple instances of Wall and update their locations in the Timer... or have the Wall to be the full size of the panel. Yes, you can achieve the same result with JComponent, as we said the only difference is it's not opaque, if you're going to fill it, then a JPanel will give better performance (at least I think so) – Frakcool Feb 23 '17 at 15:14
2
  • Don't do setLocation(wallX, wallY); within a paint method, painting should paint the current state never change it.
  • You're also mixing your own properties with the components properties, I'd suggest simply using the components pre-existing properties, since you're trying to place/size the component yourself.
  • getPreferredSize won't do anything when you're using a null layout, which probably explains part of your problem as the component assumes a default size of 0x0
  • Since Swing is not thread safe, it would be wiser to use a Swing Timer to act as your "main loop" instead of Thread

I'm not a fan of "component based animation" for this kind of purpose, preferring instead to go straight to custom painting, but that's me

But if you have to, you should try and make use of the API functionality that is available to you

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

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

    public Test() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Test");
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private WallPane wallPane;

        public TestPane() {
            setLayout(null); // :(
            wallPane = new WallPane();
            wallPane.setLocation(-100, 150);
            add(wallPane);

            Timer timer = new Timer(16, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    wallPane.moveBy(2, 0);
                    repaint();
                }
            });
            timer.start();
        }

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

    }

    public class WallPane extends JPanel {
        public WallPane() {
            setSize(100, 100);
            setBackground(Color.RED);
        }

        public void moveBy(int xDelta, int yDelta) {
            int x = getX() + xDelta;
            int y = getY() + yDelta;
            setLocation(x, y);
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Since the question has not been downvote and no new close votes have been added, I can assume there is nothing wrong with the question. In what ways does it not answer the question? In what ways could it be improved? Since no new answers have been provided, I can assume you don't have a better idea of how to solve the problem, which begs the question how you know it won't work? Since no other answers have been voted against I can only assume you have a personal issue with me helping people – MadProgrammer Feb 23 '17 at 00:07