一旦JComponent离开屏幕,它就会停止渲染

我正在尝试制作一个简单的动画,其中矩形从屏幕开始(在屏幕右边缘的右侧)并向左移动。 因此,在这种情况下,我的框架的宽度为1000,墙壁的起始值为x值为1100.显然,首先,矩形不应该对我们可见。 但随着矩形向左移动,它最终应该变得可见。 但是,这个动画不会这样做。 即使墙的x值在屏幕的范围内,也不会被渲染。

我尝试在wall的paintComponent()方法中放入一个println()语句,我发现paintComponent()没有被frame的repaint()方法调用。 我认为当墙首次添加到框架时,Swing决定,因为它不在屏幕上,所以即使墙壁最终进入屏幕,Swing认为它不需要渲染。得到渲染

我尝试重新validation框架和组件并使其无效,但没有任何效果。 请帮我解决这个问题。 以下是代码:

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

我可以在你的程序中找到一些错误

  1. 你使用的是null layout ,请看Null布局是邪恶的, 这个问题的答案就是为什么你应该避免使用它。 (可能不是在这种情况下,根据@ MadProgrammer在下面的评论),这只是另一种方法

  2. while (true) {此行可能会阻止事件调度线程(EDT)以及此行: Thread.sleep(16); ,请参阅课程:Swing中的并发以了解有关如何使用Swing Timers的更多信息。 您还应该将您的程序放在EDT上,这可以完成:

     public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { //Your constructor here } }); } 
  3. 你不是在Wall类的paintComponent()方法上调用super.paintComponent() ,这可能会破坏绘制链,总是先调用它。

  4. 你正在扩展JComponent ,最好扩展JPanel并使用Shape的API对它进行自定义绘制

考虑到以上所有因素,您可以获得如下代码:

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

这将产生类似这样的输出:

在此处输入图像描述

  • 不要做setLocation(wallX, wallY); 在绘画方法中,绘画应该绘制当前状态永远不会改变它。
  • 您还将自己的属性与组件属性混合在一起,我建议您只使用组件预先存在的属性,因为您试图自己放置/调整组件的大小。
  • 当您使用null布局时, getPreferredSize将不会执行任何操作,这可能解释了部分问题,因为组件假定默认大小为0x0
  • 由于Swing不是线程安全的,因此使用Swing Timer作为“主循环”代替Thread会更明智

我不喜欢这种用途的“基于组件的动画”,而是更喜欢直接去定制绘画,但那就是我

但是,如果必须,您应该尝试使用可用的APIfunction

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