从 ArrayList 重新绘制 class 的实例

Repainting an instance of a class from an ArrayList

好的,所以我是 Java Swing 的新手,总体来说是 Java 的初学者。我目前的问题是我设计了一个"cityscape"。我正在研究飞来飞去的不明飞行物,但我随机生成的建筑物会继续再生。 我想知道是否有一种方法可以像我尝试的那样将我的建筑物实例保存到 ArrayList,并在每次调用绘制时从该列表中绘制该选择。我尝试了我的想法的,我相信它只是在 运行 时崩溃了,因为它甚至没有打开 JFrame,而是在出错时产生错误。这是我拥有的:

城市风光class(主要class):

import java.awt.*; 
import javax.swing.*;
public class CityScape extends JPanel 
{     
  Buildings a = new Buildings ();
UFO b = new UFO();

  @Override
  public void paint (Graphics g)
  {
    //RememberBuildings.buildingList.get(1).paint(g);
    a.paint(g);
    b.paint(g);
  }
  public void move()
  {
    b.move();
  }


  public static void main(String[] args) throws InterruptedException
  { 
    JFrame frame = new JFrame("Frame"); 
    CityScape jpe = new CityScape();
    frame.add(jpe);
    frame.setSize(800, 750); 
    frame.setBackground(Color.BLACK);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    System.out.println(frame.getContentPane().getSize());
    while (true)
    {
      jpe.move(); //Updates the coordinates
      jpe.repaint(); //Calls the paint method
      Thread.sleep(10); //Pauses for a moment
    }
  }
}

建筑物class(生成建筑物的class):

import java.awt.*;

public class Buildings
{

  private int maxX = 784;
  private int maxY = 712;
  private int width = (int)(Math.random()*100+100);
  private int height = (int)(Math.random()*350+100);
  private  int rows = Math.round((height)/25);
  private int columns = Math.round(width/25);

  public void addBuilding()
  {
  RememberBuildings.addBuilding();
  }

  public void paint(Graphics g) 
  { 
    Graphics2D g2d = (Graphics2D) g;

    Color transYellow = new Color (255, 255, 0, 59);

    g2d.setColor(Color.BLACK);
    g2d.fillRect(0, 0, maxX, maxY);

    g2d.setColor(Color.WHITE);
    g2d.fillRect(5, 5, 25, 25);

    int a = 0;

    for (int i =10; i<634; i+=(a+10))//buildings
    {

      g2d.setColor(Color.GRAY);
      g2d.drawRect(i, maxY-height, width, height);
      g2d.fillRect(i, maxY-height, width, height);


      rows = Math.round((height)/25);
      columns = Math.round(width/25);

      for (int j = 1; j<=columns; j++)//windows
      {
        for (int k = 1; k<=rows; k++)
        {
          g2d.setColor(Color.BLACK);
          g2d.drawRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
          if (Math.random()<0.7)
          {
            g2d.setColor(Color.YELLOW);
            g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
          }
          else
          {
            g2d.setColor(Color.BLACK);
            g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
            g2d.setColor(transYellow);
            g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
          }
        }
      }
      addBuilding();
      a = width;
      height = (int)(Math.random()*462+100);
      width = (int)(Math.random()*100+100);

    }
  }
}

RememberBuildings class(重点是向 ArrayList 添加一个实例):

import java.util.*;
public class RememberBuildings
{
  public static ArrayList<Buildings> buildingList = new ArrayList<Buildings>();

  public static void addBuilding()
  {
    buildingList.add(new Buildings());
  }
}

最后是我的 UFO class(创建飞过的 UFO):

import java.awt.*;
import javax.swing.*;
public class UFO extends JPanel
{
  private int x = 20; //x and y coordinates of the ball
  private int y = 20;
  private int xa = 1;
  public void move() //Increase both the x and y coordinates
  {
    if (x + xa < 0) {
      xa = 1;
    }
    if (x + xa > 784-75) 
    {
      xa = -1;
    }
    x = x + xa; 
  }
  public void paint(Graphics g)
  {
    super.paint(g); //Clears the panel, for a fresh start
    Graphics2D g2d = (Graphics2D) g;
    g2d.setColor(Color.LIGHT_GRAY);
    g2d.fillOval(x,y,75,25); //Draw the ball at the desired point
  }
}

  • 避免覆盖 paint,而是使用 paintComponent。在进行任何自定义绘制之前始终调用 super 绘制方法,以确保维护绘制链。有关详细信息,请参阅 Painting in AWT and Swing and Performing Custom Painting
  • 请注意,Swing 不是线程安全的,从事件调度线程的上下文之外更新任何组件(或组件可能依赖的任何变量)是不明智的。一个简单的解决方案可能是使用 Swing Timer 而不是 while (true) 循环和 Thread.sleep。有关详细信息,请参阅 How to use Swing Timers
  • 您还应该只在事件分派线程的上下文中创建和修改 UI 组件,有关详细信息,请参阅 Initial Threads
  • 如果您的代码无法正常工作,您应该考虑提供一个 runnable example 来证明您的问题。这不是代码转储,而是您正在做的事情的一个示例,它突出了您遇到的问题。这将导致更少的混乱和更好的响应。提供不可运行且缺失的代码 类 让人很难知道为什么它不起作用以及如何修复它。

因此,每次调用 Buildings#paint 时,它都会重新生成所有构建,这是随机完成的。

public void paint(Graphics g) {
    Graphics2D g2d = (Graphics2D) g;

    Color transYellow = new Color(255, 255, 0, 59);

    g2d.setColor(Color.BLACK);
    g2d.fillRect(0, 0, maxX, maxY);

    g2d.setColor(Color.WHITE);
    g2d.fillRect(5, 5, 25, 25);

    int a = 0;

    for (int i = 10; i < 634; i += (a + 10))//buildings
    {

        g2d.setColor(Color.GRAY);
        g2d.drawRect(i, maxY - height, width, height);
        g2d.fillRect(i, maxY - height, width, height);

        rows = Math.round((height) / 25);
        columns = Math.round(width / 25);

        for (int j = 1; j <= columns; j++)//windows
        {
            for (int k = 1; k <= rows; k++) {
                g2d.setColor(Color.BLACK);
                g2d.drawRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
                if (Math.random() < 0.7) {
                    g2d.setColor(Color.YELLOW);
                    g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
                } else {
                    g2d.setColor(Color.BLACK);
                    g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
                    g2d.setColor(transYellow);
                    g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
                }
            }
        }
        addBuilding();
        a = width;
        height = (int) (Math.random() * 462 + 100);
        width = (int) (Math.random() * 100 + 100);

    }
}

有两种方法可以解决此问题,具体使用哪种方法取决于您想要实现的目标。您可以将建筑物直接渲染到 BufferedImage 并在每个绘制周期简单地绘制它,或者您可以缓存您需要的信息以重新创建建筑物。

BufferedImage方法更快,但不能动画化,所以如果你想以某种方式使建筑物动画化(让灯光闪烁),你需要建立一系列信息这使您可以简单地重新绘制它们。

我要第二次,因为你问过 ArrayList 中的绘画资产。

我首先将您的 "paint" 代码翻译成一个虚拟建筑的概念,它也有关于它自己的灯光的信息。

public class Building {

    protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);

    private int x, y, width, height;
    private List<Light> lights;

    public Building(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;

        lights = new ArrayList<>(25);
        int rows = Math.round((height) / 25);
        int columns = Math.round(width / 25);

        for (int j = 1; j <= columns; j++)//windows
        {
            for (int k = 1; k <= rows; k++) {
                Color color = null;
                if (Math.random() < 0.7) {
                    color = Color.YELLOW;
                } else {
                    color = TRANS_YELLOW;
                }
                lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
            }
        }
    }

    public void paint(Graphics2D g2d) {
        g2d.setColor(Color.GRAY);
        g2d.drawRect(x, y, width, height);
        g2d.fillRect(x, y, width, height);
        for (Light light : lights) {
            light.paint(g2d);
        }
    }

    public class Light {

        private int x, y;
        private Color color;

        public Light(int x, int y, Color color) {
            this.x = x;
            this.y = y;
            this.color = color;
        }

        public void paint(Graphics2D g2d) {
            g2d.setColor(Color.BLACK);
            g2d.fillRect(x, y, 20, 20);
            g2d.setColor(color);
            g2d.fillRect(x, y, 20, 20);
        }
    }

}

这允许您为 Building 生成主要参数并简单缓存结果,并在需要时简单地绘制它。

例如...

public class Buildings {

    private int maxX = 784;
    private int maxY = 712;

    private List<Building> buildings;

    public Buildings() {
        buildings = new ArrayList<>(25);
        for (int i = 10; i < 634; i += 10)//buildings
        {
            int width = (int) (Math.random() * 100 + 100);
            int height = (int) (Math.random() * 350 + 100);
            int x = i;
            int y = maxY - height;

            buildings.add(new Building(x, y, width, height));
        }
    }

    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        for (Building building : buildings) {
            building.paint(g2d);
        }
    }
}

我还更改了您的 UFO class,因此它不再从 JPanel 延伸,因为它只是不需要并且可能是与您的绘画混淆的主要原因.

然后我更新了您 CityScape 中的 paint 方法,改为使用 paintComponent...

public class CityScape extends JPanel {

    Buildings a = new Buildings();
    UFO b = new UFO();

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        a.paint(g);
        b.paint(g);
    }

作为一个可运行的例子...

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class CityScape extends JPanel {

    Buildings a = new Buildings();
    UFO b = new UFO();

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
        a.paint(g);
        b.paint(g);
    }

    public void move() {
        b.move();
    }

    public static void main(String[] args) throws InterruptedException {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Frame");
                CityScape jpe = new CityScape();
                frame.add(jpe);
                frame.setSize(800, 750);
                frame.setBackground(Color.BLACK);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                System.out.println(frame.getContentPane().getSize());

                Timer timer = new Timer(10, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        jpe.move(); //Updates the coordinates
                        jpe.repaint(); //Calls the paint method
                    }
                });
                timer.start();
            }
        });
    }

    public class Buildings {

        private int maxX = 784;
        private int maxY = 712;

        private List<Building> buildings;

        public Buildings() {
            buildings = new ArrayList<>(25);
            for (int i = 10; i < 634; i += 10)//buildings
            {
                int width = (int) (Math.random() * 100 + 100);
                int height = (int) (Math.random() * 350 + 100);
                int x = i;
                int y = maxY - height;

                buildings.add(new Building(x, y, width, height));
            }
        }

        public void paint(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            for (Building building : buildings) {
                building.paint(g2d);
            }
        }
    }

    public static class Building {

        protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);

        private int x, y, width, height;
        private List<Light> lights;

        public Building(int x, int y, int width, int height) {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;

            lights = new ArrayList<>(25);
            int rows = Math.round((height) / 25);
            int columns = Math.round(width / 25);

            for (int j = 1; j <= columns; j++)//windows
            {
                for (int k = 1; k <= rows; k++) {
                    Color color = null;
                    if (Math.random() < 0.7) {
                        color = Color.YELLOW;
                    } else {
                        color = TRANS_YELLOW;
                    }
                    lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
                }
            }
        }

        public void paint(Graphics2D g2d) {
            g2d.setColor(Color.GRAY);
            g2d.drawRect(x, y, width, height);
            g2d.fillRect(x, y, width, height);
            for (Light light : lights) {
                light.paint(g2d);
            }
        }

        public class Light {

            private int x, y;
            private Color color;

            public Light(int x, int y, Color color) {
                this.x = x;
                this.y = y;
                this.color = color;
            }

            public void paint(Graphics2D g2d) {
                g2d.setColor(Color.BLACK);
                g2d.fillRect(x, y, 20, 20);
                g2d.setColor(color);
                g2d.fillRect(x, y, 20, 20);
            }
        }

    }

    public class UFO {

        private int x = 20; //x and y coordinates of the ball
        private int y = 20;
        private int xa = 1;

        public void move() //Increase both the x and y coordinates
        {
            if (x + xa < 0) {
                xa = 1;
            }
            if (x + xa > 784 - 75) {
                xa = -1;
            }
            x = x + xa;
        }

        public void paint(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.fillOval(x, y, 75, 25); //Draw the ball at the desired point
        }
    }
}

这里有几件事:

  1. 要解决 paintComponent 注释并查看示例,请查看其他线程:Concerns about the function of JPanel: paintcomponent()
  2. 您所采用的逻辑与我认为有助于解决问题的面向对象编程逻辑之间似乎有些脱节(有关 OOP 的一般信息:https://en.wikipedia.org/wiki/Object-oriented_programming) :

你得到了什么:

您的结构如下:

  • CityScape :: 这是您扩展 JPanel 并设置主要功能的地方
  • UFO :: 代表 1 个 UFO
  • 的对象 class
  • Building :: class 具有绘制随机建筑物的方法和调用 RememberBuildings
  • 中的方法
  • RememberBuildings :: 我认为这是为了跟踪已绘制的建筑物

这里的问题是您的建筑 class 的绘制方法不断地绘制多个新随机化的建筑,而不是一个保留其结构的固定建筑。

我的建议:

这个问题有很多解决方案,每个解决方案都有不同的实现方式,但我的建议是以 OOP 方式改造您的建筑 class,这意味着它代表 1 座独立建筑(符合 class 的名称)。这将包含一个构造函数,该构造函数一次初始化该单个建筑物的所有随机尺寸,并在 jpanel 上绘制该单个建筑物。然后,您需要在城市景观中保留一个数组或某种列表,其中包含作为城市景观一部分的建筑物,从而消除对 "RememberBuildings" class 的需要。大致如此:

CityScape extends JPanel:
    variables:
        Building[] buildings;    //might be useful to use an arraylist/stack/queue instead of an array depending on implementation
        UFO craft;

    constructor:
        setup new Building objects and add to list buildings
        initialize craft to new UFO

    paintComponent:
        calls the paint methods for each building & the ufo craft

Building:
    variables:
        int x, y; // position of building
        int height, width; // of this building

    constructor:
        initializes x, y // probably needs to be inputed from CityScape with this setup
        calc height and width randomly // stored in this.height/width

    paint:
        paints single building based on it's variables

//side-note, you'll probably need getters for the x/y/width to build each building from CityScape

其他一切应该都差不多。

祝你好运!