我目前正在开发一个使用绘图功能为JPanel中的“反弹图像”制作动画的应用程序。为此,我必须学习使用线程。当我在代码中使用它们时,有人建议不要直接使用线程,而可以使用Executor框架和ExecutorService之类的东西。
现在,我的问题是,添加新图像时,我需要确保不会在彼此内部创建它们。当程序检测到它们将相交时,它应等待一段时间,而所有其他线程仍在运行,因此图像仍会在当前位置移动和绘制。但是,发生的是,当我让一个线程进入睡眠状态以等待某个位置为空时,整个程序似乎冻结了。似乎正在运行的唯一东西是图像移动功能。
这里是代码:
这是BouncingImages类
/* NOTE: requires MyImage.java */
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.swing.*;
public class BouncingImages extends JFrame implements ActionListener {
public static void main(String[] args) {
new BouncingImages();
}
static boolean imagesLoaded = true;
JPanel resetPanel = new JPanel();
JPanel runningPanel = new JPanel();
JPanel pausedPanel = new JPanel();
JPanel btnPanel = new JPanel();
public static AnimationPanel animationPanel;
ArrayList <MyImage> imageList = new ArrayList <MyImage>();
private volatile boolean stopRequested = false; //maybe should use AtomicBoolean
boolean isRunning = false;
boolean isReset = true;
ExecutorService service = Executors.newCachedThreadPool();
Future f;
//here is the part of the code responsible for creating a JFrame
BouncingImages() {
//set up button panel
JButton btnStart = new JButton("Start");
JButton btnResume = new JButton("Resume");
JButton btnAdd = new JButton("Add");
JButton btnAdd10 = new JButton("Add 10");
JButton btnStop = new JButton("Stop");
JButton btnReset = new JButton("Reset");
JButton btnExit = new JButton("Exit");
btnStart.addActionListener(this);
btnResume.addActionListener(this);
btnAdd.addActionListener(this);
btnAdd10.addActionListener(this);
btnStop.addActionListener(this);
btnReset.addActionListener(this);
btnExit.addActionListener(this);
resetPanel.add(btnStart);
runningPanel.add(btnAdd);
runningPanel.add(btnAdd10);
runningPanel.add(btnStop);
pausedPanel.add(btnResume);
pausedPanel.add(btnReset);
animationPanel = new AnimationPanel();
resetButtons();
this.add(btnPanel, BorderLayout.SOUTH);
this.add(animationPanel);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack(); //since the JPanel is controlling the size, we need pack() here.
this.setLocationRelativeTo(null); //after pack();
this.setVisible(true);
}
//I use different JPanels with designated buttons to make them display different buttons when the program is running, paused or completely restarted.
public void resetButtons() {
btnPanel.updateUI();
if (isReset) {
btnPanel.removeAll();
btnPanel.add(resetPanel, BorderLayout.SOUTH);
} else {
if (isRunning) {
btnPanel.removeAll();
btnPanel.add(runningPanel, BorderLayout.SOUTH);
}
if (!isRunning) {
btnPanel.removeAll();
btnPanel.add(pausedPanel, BorderLayout.SOUTH);
}
}
}
//ActionListener for Buttons
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Start")) {
startAnimation();
isRunning = true;
isReset = false;
resetButtons();
}
if (e.getActionCommand().equals("Resume")) {
startAnimation();
isRunning = true;
resetButtons();
}
if (e.getActionCommand().equals("Add")) {
addImage();
}
if (e.getActionCommand().equals("Add 10")) {
for(int i = 0; i <10; i++) {
addImage();
startAnimation();
}
}
if (e.getActionCommand().equals("Stop")) {
pauseAnimation();
isRunning = false;
resetButtons();
}
if (e.getActionCommand().equals("Reset")) {
imageList.clear();
repaint();
isReset = true;
resetButtons();
}
if (e.getActionCommand().equals("Exit")) {
System.exit(0);
}
}
//This function starts all the animations using the Runnable AnimationThread
void startAnimation() {
//this starts the program
if (f == null) {
f = service.submit(new AnimationThread());
}
//this starts the program after it got paused
else if (f.isCancelled()) {
f = service.submit(new AnimationThread());
}
}
//this pauses all the animations
void pauseAnimation() {
f.cancel(true);
}
//here is the part of the code that I have problems with. I'm not sure how to make the program wait for a spot to be empty while all the other threads are running rather than pausing them all.
void addImage(){
int i = 0;
MyImage image;
while (true) {
image= new MyImage("image.png");
if (checkCollision(image)) {
if(i > 100){
System.out.println("Something went wrong");
System.exit(0);
}
try {
i++;
Thread.sleep(50);
} catch (InterruptedException e) {}
} else {
System.out.println("image added");
break;
}
}
imageList.add(image);
}
//this part of the program does all the image moving. It uses the function move image from the MyImage class and a checkCollision function from this class.
void moveAllImages() {
for (MyImage image : imageList) {
image.moveImage(animationPanel);
image.calculatePoints();
checkCollision(image);
}
}
//this checks all the collisions with other images. It can be used for checking if a image can be created in some spot and also for all the bouncing callculations
boolean checkCollision(MyImage currentImage){
if(imageList.isEmpty()) return false;
for(MyImage im : imageList){
if(currentImage == im) continue;
if(currentImage.intersects(im)){
if(im.contains(currentImage.ml) || im.contains(currentImage.mr)){
currentImage.undoMove();
currentImage.vx = -currentImage.vx;
return true;
}
}
if(im.contains(currentImage.mt) || im.contains(currentImage.mb)){
currentImage.undoMove();
currentImage.vy = - currentImage.vy;
return true;
}
if(currentImage.contains(im.ml) || currentImage.contains(im.mr)){
currentImage.undoMove();
currentImage.vx = -currentImage.vx;
return true;
}
if (im.contains(currentImage.tl) || im.contains(currentImage.tr) || im.contains(currentImage.bl) || im.contains(currentImage.br)) {
currentImage.undoMove();
currentImage.vx *= -1;
currentImage.vy *= -1;
return true;
}
}
return false;
}
//This class does drawing graphics and nothing else
public class AnimationPanel extends JPanel {
AnimationPanel() {
this.setBackground(Color.BLACK);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.setPreferredSize(new Dimension((int) screenSize.getWidth() / 2, (int) screenSize.getHeight() / 2));
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (MyImage im : imageList) {
g.drawImage(im.image, im.x, im.y, im.width, im.height, null);
}
}
}
//this is the Runnable AnimationThread which does all the image moving and repainting.
private class AnimationThread implements Runnable {
@Override
public void run() {
while (!f.isCancelled()) {
moveAllImages();
animationPanel.repaint();
try {
Thread.sleep(5);
}
catch (InterruptedException e) {
System.out.println(e.getMessage());
f.cancel(true);
}
}
}
}
}
这是MyImage类
package bouncer;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
//NOTE: This class requires BouncingImages.java
/* This class combines an image with a rectangle
* which allows for easy movement and collision detection
*/
class MyImage extends Rectangle {
//this gives it an x,y,width,height
static int biggestDim = 0;
BufferedImage image;
Color color = Color.RED;
//these are the speeds of movement
int vx = 1;
int vy = 1;
int lastx = -1;
int lasty = -1;
Point ctr, tl, tr, bl, br, ml, mr, mt, mb;
MyImage(String filename) {
vx = (int) (Math.random() * 5 + 1);
vy = (int) (Math.random() * 5 + 1);
//load the image
try {
image = ImageIO.read(new File(filename));
width = image.getWidth(null);
height = image.getHeight(null);
}
catch (IOException e) {
System.out.println("ERROR: image file \"" + filename + "\" not found");
//e.printStackTrace();
BouncingImages.imagesLoaded = false;
width = 100 + (int) (Math.random() * 100);
height = width - (int) (Math.random() * 70);
color = Color.getHSBColor((float) Math.random(), 1.0f, 1.0f); // a quick way to get random colours
//System.exit(0);
}
//update the variable containing the biggest dimension
if (width > biggestDim) biggestDim = width;
if (height > biggestDim) biggestDim = height;
calculatePoints();
}
void calculatePoints() {
//Calculate points
//corners
tl = new Point(x, y);
tr = new Point(x + width, y);
bl = new Point(x, y + height);
br = new Point(x + width, y + height);
//center
ctr = new Point(x + width / 2, y + height / 2);
//mid points of sides
ml = new Point(x, y + height / 2);
mr = new Point(x + width, y + height / 2);
mt = new Point(x + width / 2, y);
mb = new Point(x + width / 2, y + height);
}
void moveImage(BouncingImages.AnimationPanel panel) {
lastx = x;
lasty = y;
x += vx;
y += vy;
if (x < 0 && vx < 0) {
x = 0;
vx = -vx;
}
if (y < 0 && vy < 0) {
y = 0;
vy = -vy;
}
if (x + width > panel.getWidth() && vx > 0) {
x = panel.getWidth() - width;
vx = -vx;
}
if (y + height > panel.getHeight() && vy > 0) {
y = panel.getHeight() - height;
vy = -vy;
}
}
void undoMove() {
if (lastx > 0) {
x = lastx;
y = lasty;
}
lastx = lasty = -1;
}
}
您正在addImage
中的方法Thread.sleep(50)
中阻塞主线程。相反,您可以确保异步调用方法addImage
,例如
if (e.getActionCommand().equals("Add")) {
CompletableFuture.runAsync(this::addImage);
}
if (e.getActionCommand().equals("Add 10")) {
CompletableFuture.runAsync(() -> addImages(10));
}
与
private void addImages(int numberOfImages) {
for(int i = 0; i < numberOfImages; i++) {
addImage();
startAnimation();
}
}
如果您想对线程做更多的试验,那么您可以考虑如何“手动”进行操作。