简体   繁体   中英

JComponent stops getting rendered once it goes off the screen

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);
    }
}

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 Shape s 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:

在此处输入图片说明

  • 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);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM