问题: 在JFrame上显示动画的问题。似乎我很想/不太了解Java的图形是如何工作的。
全球创意: 让我们说我想做一个游戏/电影/剪辑。为此我需要这个(不)简单的动画才能正常工作。
此问题的一个示例: 我得到了类Screen,它有屏幕内容 - JFrame的声明,设置它的配置(大小,关闭操作等),然后创建类Box的对象,在框架上显示。请检查这个图像/图表(希望我用正确的方式编写):ClassesDiagram
现在,类Box扩展了JPanel。我从JPanel继承了方法Paint()并覆盖它,绘制框。
在Screen类中,在我创建了两个Box之后,i。添加()它们到JFrame。接下来,在(true)开始循环,并通过使线程睡眠该量来每200毫秒更新盒子的位置。 (在这种情况下,简单的x ++或y ++取决于哪个方框,box1或box2)。
主要问题1):程序运行,并显示JFrame,但在JFrame上,它显示仅最后添加的对象/组件 - Box。它没有显示另一个。为什么呢?
我找到了一个主题,How to add multiple components to a JFrame?,并尝试了最受欢迎的帖子给出的提示,由 jjnguy于2010年11月15日17:02 。 由于一些奇怪的原因,不是第一个,也不是第二个提示对我有效。
主要问题2):据我所知,我需要布局管理器。如果我只想在框架上的特定X,Y处绘制(),为什么需要它呢?
发现其他帖子(无法再找到它)+ Oracle关于布局的指导方针,建议我需要考虑使用setLayout(null);我试图这样做,但后来又出现了问题。
主要问题3): JFrame显示,它只显示1个框(绿色不会显示,无论你做什么。不知道为什么)以及何时移动 - 它会从另一边消失。 这里:Box Movement
提前感谢您提供任何帮助,提示和解释!希望帖子清晰,有条理,外观漂亮。
public class Screen {
public static void main(String[] args) {
new Screen();
}
private JFrame window;
public Screen() {
window = new JFrame("Multiply components panel");
//window.setLayout(null);
window.setSize(200, 200);
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
Box b1 = new Box(10,10, "box1");
//b1.setBounds(10, 10, 50, 50);
Box b2 = new Box(100,100, "box2");
//b2.setBounds(100, 100, 50, 50);
window.add(b1);
//window.add(b2);
window.setVisible(true);
while (true){
b1.update();
b2.update();
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
public class Box extends JPanel{
int x, y, w, h;
private String name;
public Box(int x, int y, String name) {
this.x = x;
this.y = y;
this.w = 100;
this.h = 100;
this.name=name;
}
public void paint(Graphics g){
System.out.println(this.name.equalsIgnoreCase("box1"));
if(this.name.equalsIgnoreCase("box1")){
g.setColor(Color.BLACK);
g.fillRect(x, y, w, h);
}else{
g.setColor(Color.GREEN);
g.fillRect(x, y, w, h);
}
}
public void update() {
if(this.name.equalsIgnoreCase("box1"))
x++;
else
y++;
//this.setBounds(x, y, w, h);
System.out.println("Current "+name+": X: "+x+", Y: "+y+", W: "+w+", H: "+h);
repaint();
}
}
答案 0 :(得分:3)
主要问题1):程序运行,并显示JFrame,但在 JFrame它只显示最后添加的对象/组件 - Box。它没有 展示另一个。为什么呢?
你这样做
window.add(b1);
window.add(b2);
,
默认情况下JFrame
有BorderLayout
,因此当您执行add(..)
时,您将替换最后添加的框。
主要问题2):据我所知,我需要布局管理器。为什么 我需要它,如果我只想在框架上的特定X,Y处绘制()?
如果您使用JPanel
作为游戏中的对象,那么这将是唯一一次使用setLayout(null)
,因为我们希望完全控制JPanel
的放置。
主要问题3):JFrame显示,它只显示1个框(绿色 一个人不会出现,无论你会做什么。不知道为什么)和什么时候 移动 - 它从另一侧被擦除。这里:盒子运动
因为你这样做g.fillRect(x,y,w,h)
,它应该是g.fillRect(0,0,w,h)
其他问题:
1)我认为你遇到的一个主要问题是:
public class Box extends JPanel{
...
public void paint(Graphics g){
...
}
}
您应该覆盖paintComponent
的{{1}},并记得在重写方法中调用JPanel
作为第一次调用。
3)您应覆盖super.paintComponent(g)
的{{1}}并返回与getPreferredSize
4)不要在JPanel
上拨打JPanel
使用正确的setSize
和/或覆盖JFrame
必要的容器,而不是简单地在{{1}上拨打Layoutmanager
在将其设置为可见之前。
5)正如@MadProgrammer所说,Concurrency in Swing上有一个读数,但基本上所有的Swing组件都应该通过getPreferredSize
块在EDT上创建/操作。
6)这样做肯定是坏事:
pack()
因为您不仅创建了一个连续循环,而且还在您创建GUI的线程上调用JFrame
,因此似乎会冻结。请查看How to use Swing Timers,另请参阅this类似问题。关于上述主题。
以下是实施上述修复程序的代码:
SwingUtilities.inokeXXX
最后看看我的教程/代码段Game Development Loop, Logic and Collision detection Java Swing 2D。
它拥有开始简单的2D游戏所需的一切,如游戏循环,逻辑,像素碰撞检测,动画(即在多个精灵之间交换以创建精灵集的动画)等等,但根本区别在于它使用对象作为游戏实体,即一个类将保存要绘制的对象所需的所有信息,IMO这是游戏应该如何完成,一些事情可能会得到图形强烈,并且有许多JPanels想知道屏幕肯定会降低整体FPS
答案 1 :(得分:1)
我意识到这是一个老问题,但它已被浏览了 1,000 多次。
在创建 Swing GUI 时,最好使用 model / view / controller (MVC) 模式。这种模式允许我们将关注点分开并一次专注于 GUI 的一个部分。如果有问题,那么我们很清楚问题出在代码中的哪个位置。
这是我创建的 GUI。
那么,让我们从模型开始。这是 Box
类。
public class Box {
private final Color color;
private final Point boxMotion;
private final Rectangle rectangle;
private final String name;
public Box(String name, Color color, Rectangle rectangle,
Point boxMotion) {
this.name = name;
this.color = color;
this.rectangle = rectangle;
this.boxMotion = boxMotion;
}
public void update(int width, int height) {
this.rectangle.x += boxMotion.x;
this.rectangle.y += boxMotion.y;
int boxWidth = rectangle.x + rectangle.width;
int boxHeight = rectangle.y + rectangle.height;
if (rectangle.x < 0) {
rectangle.x = -rectangle.x;
boxMotion.x = -boxMotion.x;
}
if (boxWidth > width) {
rectangle.x = width - rectangle.width - boxMotion.x;
boxMotion.x = -boxMotion.x;
}
if (rectangle.y < 0) {
rectangle.y = -rectangle.y;
boxMotion.y = -boxMotion.y;
}
if (boxHeight > height) {
rectangle.y = height - rectangle.height - boxMotion.y;
boxMotion.y = -boxMotion.y;
}
}
public Color getColor() {
return color;
}
public Rectangle getRectangle() {
return rectangle;
}
public String getName() {
return name;
}
}
Box
类是一个普通的 Java 类。它具有典型的 getter 方法。没有 setter 类,因为构造函数设置了所有内部字段。
我们分别使用 java.awt.Rectangle
和 java.awt.Point
来保存框的大小和框运动的方向。这使我们不必定义六个 int
字段。
通过保存每个框的Color
和运动方向,我们可以有许多不同颜色和不同方向的框。运动方向在技术上是一个 delta X 和 delta Y 实例,但我不打算创建一个单独的类来说明这一点。 Point
类已经足够好了。
update
方法最难改正。 update
方法控制框的运动,并检查框是否碰到绘图 JPanel
的边缘。此方法包含在 Box
类中,因为它作用于每个框。该方法将由控制器执行。
MVC 模式并不意味着模型、视图和控制器代码驻留在单独的类中,尽管这是理想的。代码可以放在最有意义的地方,只要代码的执行被模型、视图和控制器分开即可。
接下来,我们将看看 Boxes
类。
public class Boxes {
private final int drawingPanelWidth;
private final int drawingPanelHeight;
private final List<Box> boxes;
public Boxes() {
this.drawingPanelWidth = 600;
this.drawingPanelHeight = 400;
this.boxes = new ArrayList<>();
addBoxesFactory();
}
private void addBoxesFactory() {
Rectangle rectangle = new Rectangle(10, 10, 50, 50);
Point point = new Point(3, 3);
this.boxes.add(new Box("box1", Color.BLACK, rectangle, point));
rectangle = new Rectangle(100, 100, 50, 50);
point = new Point(-3, -3);
this.boxes.add(new Box("box2", Color.GREEN, rectangle, point));
}
public int getDrawingPanelWidth() {
return drawingPanelWidth;
}
public int getDrawingPanelHeight() {
return drawingPanelHeight;
}
public List<Box> getBoxes() {
return boxes;
}
}
Boxes
类是一个普通的 Java 类。它具有典型的 getter 方法。没有 setter 类,因为构造函数设置了所有内部字段。
我们使用 List
来保存我们想要显示的任意数量的 Box
实例。在这个类中,我们定义了两个 Box
实例。我们可以根据需要定义任意数量的 Box
实例。其余的代码将处理我们定义的尽可能多的框。
我们在这个类中定义了绘图JPanel
的宽度和高度。我们这样做是因为绘图 JPanel
需要这些尺寸来调整自身大小,并且因为控制器需要这些尺寸来确定 Box
实例何时到达宽度或高度边缘。
接下来,我们将看看主视图类,BoxMotionGUI
类。
public class BoxMotionGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new BoxMotionGUI());
}
private Boxes boxes;
private DrawingPanel drawingPanel;
public BoxMotionGUI() {
this.boxes = new Boxes();
}
@Override
public void run() {
JFrame frame = new JFrame("Multiple Components GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(boxes);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
Timer timer = new Timer(20, new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
for (Box box : boxes.getBoxes()) {
box.update(boxes.getDrawingPanelWidth(),
boxes.getDrawingPanelHeight());
drawingPanel.repaint();
}
}
});
timer.setInitialDelay(0);
timer.start();
}
}
BoxMotionGUI
类以调用 SwingUtilities
invokeLater
方法开始。此方法确保 Swing 应用程序将在 Event Dispatch Thread 上创建 Swing 组件。编写良好的 Swing 程序使用并发来创建永不“冻结”的用户界面。
构造函数创建 Boxes
类的一个实例。通常,您创建模型并将模型传递给视图。视图从模型中读取,但不会以任何方式更新模型。控制器类将更新模型并更新/重绘视图。
run 方法包含创建 JFrame
的所有代码。这些方法必须按此顺序调用。这个顺序是我在大多数 Swing 应用程序中使用的顺序。
java.swing.Timer
代码构成控制器。我把这段代码放在视图方法中,因为它很短。如果 actionPerformed
代码涉及更多,则需要单独的控制器类。
actionPerformed
方法更新模型并重新绘制视图。
接下来,我们将看看 DrawingPanel
类。
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private final Boxes boxes;
public DrawingPanel(Boxes boxes) {
this.boxes = boxes;
this.setPreferredSize(new Dimension(
boxes.getDrawingPanelWidth(),
boxes.getDrawingPanelHeight()));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Box box : boxes.getBoxes()) {
g2d.setColor(box.getColor());
Rectangle rectangle = box.getRectangle();
g2d.fillRect(rectangle.x, rectangle.y,
rectangle.width, rectangle.height);
}
}
}
DrawingPanel
类绘制 Box
实例。我们扩展 JPanel
是因为我们想要覆盖 paintComponent
方法。您应该扩展 Swing 组件或任何 Java 类的唯一原因是,如果您想覆盖一个或多个类方法。
paintComponent
方法绘制。它不会尝试做任何其他事情。由于每次重新绘制 GUI 时都会调用此方法,因此您希望进行尽可能少的处理。除了绘画之外的所有其他处理都转移到其他地方。
最后,这是完整的可运行代码。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class BoxMotionGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new BoxMotionGUI());
}
private Boxes boxes;
private DrawingPanel drawingPanel;
public BoxMotionGUI() {
this.boxes = new Boxes();
}
@Override
public void run() {
JFrame frame = new JFrame("Multiple Components GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(boxes);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
Timer timer = new Timer(20, new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
for (Box box : boxes.getBoxes()) {
box.update(boxes.getDrawingPanelWidth(),
boxes.getDrawingPanelHeight());
drawingPanel.repaint();
}
}
});
timer.setInitialDelay(0);
timer.start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private final Boxes boxes;
public DrawingPanel(Boxes boxes) {
this.boxes = boxes;
this.setPreferredSize(new Dimension(
boxes.getDrawingPanelWidth(),
boxes.getDrawingPanelHeight()));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Box box : boxes.getBoxes()) {
g2d.setColor(box.getColor());
Rectangle rectangle = box.getRectangle();
g2d.fillRect(rectangle.x, rectangle.y,
rectangle.width, rectangle.height);
}
}
}
public class Boxes {
private final int drawingPanelWidth;
private final int drawingPanelHeight;
private final List<Box> boxes;
public Boxes() {
this.drawingPanelWidth = 600;
this.drawingPanelHeight = 400;
this.boxes = new ArrayList<>();
addBoxesFactory();
}
private void addBoxesFactory() {
Rectangle rectangle = new Rectangle(10, 10, 50, 50);
Point point = new Point(3, 3);
this.boxes.add(new Box("box1", Color.BLACK, rectangle, point));
rectangle = new Rectangle(100, 100, 50, 50);
point = new Point(-3, -3);
this.boxes.add(new Box("box2", Color.GREEN, rectangle, point));
}
public int getDrawingPanelWidth() {
return drawingPanelWidth;
}
public int getDrawingPanelHeight() {
return drawingPanelHeight;
}
public List<Box> getBoxes() {
return boxes;
}
}
public class Box {
private final Color color;
private final Point boxMotion;
private final Rectangle rectangle;
private final String name;
public Box(String name, Color color, Rectangle rectangle,
Point boxMotion) {
this.name = name;
this.color = color;
this.rectangle = rectangle;
this.boxMotion = boxMotion;
}
public void update(int width, int height) {
this.rectangle.x += boxMotion.x;
this.rectangle.y += boxMotion.y;
int boxWidth = rectangle.x + rectangle.width;
int boxHeight = rectangle.y + rectangle.height;
if (rectangle.x < 0) {
rectangle.x = -rectangle.x;
boxMotion.x = -boxMotion.x;
}
if (boxWidth > width) {
rectangle.x = width - rectangle.width - boxMotion.x;
boxMotion.x = -boxMotion.x;
}
if (rectangle.y < 0) {
rectangle.y = -rectangle.y;
boxMotion.y = -boxMotion.y;
}
if (boxHeight > height) {
rectangle.y = height - rectangle.height - boxMotion.y;
boxMotion.y = -boxMotion.y;
}
}
public Color getColor() {
return color;
}
public Rectangle getRectangle() {
return rectangle;
}
public String getName() {
return name;
}
}
}