一、项目简介
创建一个窗口页面,点击即可生成一个随机大小、随机颜色、随机速度的小球,小球间会相互碰撞,碰撞之后将反弹,小球同样会与窗口边界发生碰撞。窗口内存在暂停和清除页面按钮,用于控制小球的运动状态,以及清空页面和存储的数据。也可通过单击或双击小球对单个小球实现暂停或清除功能。总而言之是一个简单的随机动画。
二、功能概述
同上,需要实现的操作就是暂停和清除,还有将小球数据存储在文件中,具体就是把数据用逗号分割成字段,再将字段存储在字符串数组中,进行保存,这样下次可以从中解析字段,在窗口中直接加载出已存储的小球。
三、代码体现
1.首先我要创建出一个窗口,最好是一个带有按钮的弹窗,这样方便下一步的添加按钮操作。我最初的设想只是设计一个窗口,再添加按钮,绘制小球。也许是绘制小球需要把原有的轨迹遮盖,导致我后续添加的按钮无法持续显示在窗口中,最后我放弃了原来的代码,使用了现在的框架。
新建一个类,命名MyStart
import javax.swing.SwingUtilities;
public class MyStart {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
ButtonFrame frame = new ButtonFrame();
frame.setVisible(true);
});
}
}
这样使得后续的操作更方便,多线程也更好的提升了运行性能,减少出错。
还有就是学会使用Lambda表达式可以很好的简化代码。
2.现在有了一个最初始的窗口界面,就可以继续设计这个窗口的布局。定义一个ButtonFrame类,新建对象,先给出一个标题,确定窗口的尺寸,然后把窗口置于中间,这样方便观看。然后创建一个按钮面板,采用流式布局放到窗口中间,比较美观,而且在窗口上增添面板就可以解决按钮被覆盖的问题,就像在画板上放上画纸后再绘画。
新建出需要的按钮对象
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
class ButtonFrame extends JFrame {
private BallPanel panel;
public ButtonFrame() {
setTitle("随机小球动画");
setSize(800, 600);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new BallPanel();
add(panel);
panel.loadShapes();
JPanel buttonPanel = new JPanel(); // 创建按钮面板
buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER)); // 设置布局
// 暂停和继续按钮
JButton pauseButton = new JButton("Pause/Restart");
pauseButton.addActionListener(e -> panel.toggleAnimation());
buttonPanel.add(pauseButton); // 将按钮添加到面板
// 清空按钮
JButton clearButton = new JButton("Clear Shapes");
clearButton.addActionListener(e -> panel.clearShapes());
buttonPanel.add(clearButton); // 将按钮添加到面板
add(buttonPanel, BorderLayout.SOUTH); // 将按钮面板添加到框架
由于关闭窗口时需要保存数据,以便下次读取,即关闭窗口也是一个操作,所以设置窗口监听,调用后面BallPanel的saveShapes操作。
// 当关闭窗口时,保存图形形态,再次打开时恢复
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
panel.saveShapes();
}
});
}
3.同样的,小球也用面板实现。我们先给出小球的定义,设计各个数据,如大小,速度,颜色等,以及运动状态和轨迹。我用了三种颜色的随机数值混合出小球颜色,稍微麻烦点,但是出来的颜色更好看,更柔和。小球运动会产生相应的碰撞,也体现在相应的代码中。
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;
import javax.print.attribute.standard.MediaSize.Other;
class BallShape implements Runnable {
private BallPanel panel; // 引用panel
private Point position;
private int radius;
private boolean paused;
private Random random;
private Color color;
// 移动方向和速度字段
private double dx, dy;
private double angle;
private double speed;
public BallShape(BallPanel panel, Point initialPosition, int radius) {
this.panel = panel;
this.position = initialPosition;
this.random = new Random();
if (radius <= 0)
this.radius = random.nextInt(20) + 10;
else
this.radius = radius;
paused = false;
// 初始化移动方向和速度
angle = random.nextDouble() * 2 * Math.PI;
speed = 5 + random.nextDouble() * 5; // 速度在5到10之间
dx = Math.cos(angle) * speed;
dy = 1;//Math.sin(angle) * speed;
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
this.color = new Color(r, g, b);
}
public void setPaused(boolean paused) {
this.paused = paused;
}
public void draw(Graphics g) {
g.setColor(color);
g.fillOval(position.x - radius, position.y - radius, radius * 2, radius * 2);
}
public void setColor(Color color) {
this.color = color;
}
@Override
public void run() {
while (true) {
if (!paused) {
// 移动逻辑
position.x += dx;
// position.y += dy;
// 使形状沿着正弦轨迹移动
position.y += dy * Math.sin(position.x / 100.0) * 10;
// 碰撞检测和响应
for (BallShape other : panel.getShapes()) {
if (this != other && this.collidesWith(other)) {
dx = -dx;
position.x += dx;
dy = -dy;
position.y += dy * Math.sin(position.x / 100.0) * 10;
}
}
// 保持形状在面板内
if (position.x < radius || position.x > panel.getWidth() - radius) {
dx = -dx;
position.x += dx;
}
if (position.y < radius || position.y > panel.getHeight() - radius) {
dy = -dy;
position.y += dy * Math.sin(position.x / 100.0) * 10;
}
}
panel.repaint();
try {
Thread.sleep(50); // 控制动画速度
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public boolean isPaused() {
return paused;
}
public boolean contains(Point p) {
double distance = Math.sqrt(Math.pow(position.x - p.x, 2) + Math.pow(position.y - p.y, 2));
return distance <= radius;
}
@Override
public String toString() {
return position.x + "," + position.y + "," + radius + "," + paused + "," + color.getRed() + "," + color.getGreen() + "," + color.getBlue();
}
// 检测碰撞方法
public boolean collidesWith(BallShape other) {
double distance = position.distance(other.position);
return distance < this.radius + other.radius;
}
}
接下来设计的是小球面板,包括的操作有单击暂停小球和双击清除小球,以及小球数据的存储与读取。这两个方面由我的组员负责实现,因此在BallPanel中我只负责部分方法的重写。
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Scanner;
import java.util.concurrent.CopyOnWriteArrayList;
import java.awt.color.*;
import javax.swing.JPanel;
import javax.swing.plaf.multi.MultiInternalFrameUI;
class BallPanel extends JPanel {
private CopyOnWriteArrayList<BallShape> shapes;
private boolean paused;
public BallPanel() {
shapes = new CopyOnWriteArrayList<>();
paused = false;
//鼠标监听,双击时清除小球
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
removeShapeAt(e.getPoint());
} else {
for (BallShape shape : shapes) {
if (shape.contains(e.getPoint())) {
shape.setPaused(!shape.isPaused());
repaint();
return;
}
}
addNewShape(e.getPoint());
}
}
});
}
public CopyOnWriteArrayList<BallShape> getShapes() {
return shapes;
}
public void toggleAnimation() {
paused = !paused;
for (BallShape shape : shapes) {
shape.setPaused(paused);
}
}
private void addNewShape(Point point) {
BallShape newShape = new BallShape(this, point, 0);
shapes.add(newShape);
new Thread(newShape).start();
}
private void removeShapeAt(Point point) {
shapes.removeIf(shape -> shape.contains(point));
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (BallShape shape : shapes) {
shape.draw(g);
}
}
public void saveShapes() {
try (PrintWriter out = new PrintWriter("shapes.txt")) {
for (BallShape shape : shapes) {
out.println(shape.toString());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void loadShapes() {
try (Scanner in = new Scanner(new File("shapes.txt"))) {
shapes.clear();
while (in.hasNext()) {
String line = in.nextLine();
String[] parts = line.split(",");
int x = Integer.parseInt(parts[0]);
int y = Integer.parseInt(parts[1]);
int radius = Integer.parseInt(parts[2]);
boolean paused = Boolean.parseBoolean(parts[3]);
int r = Integer.parseInt(parts[4]);
int g = Integer.parseInt(parts[5]);
int b = Integer.parseInt(parts[6]);
Color color = new Color(r, g, b);
// int x = in.nextInt();
// int y = in.nextInt();
// int radius = in.nextInt();
BallShape shape = new BallShape(this, new Point(x, y), radius);
shape.setPaused(paused);
shape.setColor(color);
shapes.add(shape);
new Thread(shape).start();
}
} catch (FileNotFoundException e) {
// 文件未找到,可以忽略或记录日志
e.printStackTrace();
}
}
public void clearShapes() {
shapes.clear();
repaint();
}
}
以上就是所有代码 ,运行的时候可能要手动设置一下desktop。
成品如下:
虽然是简单的东西,但也花费了很大的精力去学习。