
Java Swing是一个单线程的GUI工具包,这意味着所有与用户界面组件相关的操作(包括创建、更新和绘制)都必须在特定的线程上执行,这个线程被称为事件调度线程(Event Dispatch Thread, EDT)。
直接在EDT上执行耗时操作(如文件I/O、网络请求或复杂的计算)会导致UI冻结,因为它会阻塞EDT,使其无法响应用户输入或处理重绘事件。反之,如果尝试在EDT之外的线程直接修改UI组件的状态或调用其方法,则可能导致不可预测的行为、视觉错误甚至死锁,因为Swing组件并非线程安全的。
因此,在Swing中实现动画或任何需要后台处理的功能时,必须遵循以下两个核心原则:
在初学者尝试实现动画时,一个常见的误区是让动画对象(例如本例中的Circle)继承Thread并直接在run方法中更新自身位置,然后期待UI能自动重绘。原始代码中的Circle类继承了Thread,并在其run方法中通过move()更新坐标,然后调用Thread.sleep(100)。这种做法存在以下几个问题:
立即学习“Java免费学习笔记(深入)”;
正确的做法是将动画逻辑(即模型状态的更新)与UI绘制逻辑(即视图的呈现)分离。动画模型的状态可以在EDT之外的线程更新(如果耗时),但触发UI重绘和实际的UI绘制必须发生在EDT上。对于周期性、轻量级的动画,javax.swing.Timer是最佳选择。
javax.swing.Timer是Swing提供的一个轻量级定时器,它专门设计用于在EDT上触发事件。这意味着通过Timer执行的代码将自动在EDT上运行,从而避免了线程安全问题。
以下是如何使用Swing Timer实现圆形动画的详细步骤和代码示例:
首先,确保Swing应用程序的启动代码在EDT上执行。这通过EventQueue.invokeLater()实现。
import java.awt.EventQueue;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
// 确保所有UI相关的初始化都在EDT上进行
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Swing Timer 动画示例");
frame.add(new TestPane()); // 添加自定义的JPanel
frame.pack(); // 根据组件的首选大小调整窗口大小
frame.setLocationRelativeTo(null); // 窗口居中
frame.setVisible(true); // 显示窗口
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭操作
}
});
}
}TestPane是一个JPanel的子类,它负责管理动画对象(Circle实例)的集合,并通过Swing Timer驱动动画逻辑和UI重绘。
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.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.Timer; // 注意是javax.swing.Timer
public class TestPane extends JPanel {
private List<Circle> circles = new ArrayList<>();
private Timer timer; // Swing Timer实例
public TestPane() {
// 初始化圆形对象
circles.add(new Circle(100, 100, 2, 2, new int[]{50, 50, 250, 250}, Color.blue)); // 修正边界
circles.add(new Circle(150, 150, -2, 0, new int[]{50, 50, 250, 250}, Color.red));
circles.add(new Circle(50, 150, 2, -2, new int[]{50, 50, 250, 250}, Color.green));
}
@Override
public Dimension getPreferredSize() {
// 定义面板的首选大小
return new Dimension(300, 300);
}
/**
* 当组件被添加到可显示层次结构时调用。
* 在这里启动Timer,确保组件可见时动画开始。
*/
@Override
public void addNotify() {
super.addNotify();
if (timer != null) {
timer.stop(); // 停止旧的Timer以防万一
}
// 创建并启动Timer
// 5毫秒间隔,ActionListener将在EDT上执行
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 遍历所有圆形,更新它们的状态
for (Circle circle : circles) {
circle.tick();
}
repaint(); // 请求重绘面板,这也会在EDT上执行
}
});
timer.start(); // 启动Timer
}
/**
* 当组件从可显示层次结构中移除时调用。
* 在这里停止Timer,释放资源。
*/
@Override
public void removeNotify() {
super.removeNotify();
if (timer != null) {
timer.stop(); // 停止Timer
}
timer = null; // 清除引用
}
/**
* 自定义绘制方法。
* 推荐在JComponent子类中使用paintComponent而不是paint。
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 必须调用父类的paintComponent方法
Graphics2D g2d = (Graphics2D) g.create(); // 创建Graphics副本,安全操作
try {
for (Circle circle : circles) {
g2d.setColor(circle.getColor());
g2d.fillOval(circle.getX(), circle.getY(), 10, 10); // 绘制圆形
}
} finally {
g2d.dispose(); // 释放Graphics资源
}
}
}Circle类现在是一个纯粹的模型类,它不再继承Thread。它只负责维护自身的状态(位置、速度、颜色、边界)以及更新这些状态的逻辑。
import java.awt.Color;
public class Circle {
private int x, y;
private int xs, ys; // x和y方向的速度
private Color color;
private int[] boundries; // 边界:[minX, minY, maxX, maxY]
public Circle(int x, int y, int xs, int ys, int[] boundries, Color color) {
this.x = x;
this.y = y;
this.xs = xs;
this.ys = ys;
this.boundries = boundries;
this.color = color;
}
/**
* 更新圆形状态的“一帧”。
* 由Swing Timer的ActionListener调用。
*/
public void tick() {
move();
}
/**
* 更新圆形位置。
*/
protected void move() {
updateSpeed(); // 先检查并更新速度方向
this.x += this.xs;
this.y += this.ys;
}
/**
* 检查边界碰撞并反转速度方向。
* 修正了原始代码中的边界判断逻辑。
*/
protected void updateSpeed() {
// X轴边界检查
if (x <= boundries[0]) { // 触及左边界
xs *= -1;
x = boundries[0]; // 将位置校正到边界上
} else if (x >= boundries[2]) { // 触及右边界
xs *= -1;
x = boundries[2]; // 将位置校正到边界上
}
// Y轴边界检查
if (y <= boundries[1]) { // 触及上边界
ys *= -1;
y = boundries[1]; // 将位置校正到边界上
} else if (y >= boundries[3]) { // 触及下边界
ys *= -1;
y = boundries[3]; // 将位置校正到边界上
}
}
// Getter方法
public int getX() {
return x;
}
public Color getColor() {
return color;
}
public int getY() {
return y;
}
}在Java Swing中实现动画和并发操作,核心在于理解并遵循Swing的单线程模型。通过利用EventQueue.invokeLater()确保UI初始化在EDT上,并使用javax.swing.Timer安全地驱动动画逻辑和UI重绘,我们可以避免常见的线程问题,构建出响应迅速、视觉流畅的Swing应用程序。将动画模型(如Circle)与UI绘制逻辑(如TestPane的paintComponent)清晰分离,是构建可维护和可扩展Swing应用的关键。
以上就是Java Swing动画与并发:使用Swing Timer构建流畅用户界面的详细内容,更多请关注php中文网其它相关文章!
Windows激活工具是正版认证的激活工具,永久激活,一键解决windows许可证即将过期。可激活win7系统、win8.1系统、win10系统、win11系统。下载后先看完视频激活教程,再进行操作,100%激活成功。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号