Java Applet跳动小人动画源码包:含HTML调用示例与详细说明
简介:这个资源包提供一个可直接运行的Java跳动小人动画实现,核心是基于Applet的轻量级图形动画程序。里面包含boy.java源代码文件,已编译好的boy.class字节码,两个HTML调用页面(boy.html和跳动的小人.htm),一份纯文本说明文档(说明.txt),以及Word格式的操作指引(boy_html.doc)。整个动画通过Java绘图API绘制小人图形,利用线程控制实现周期性上下跳动效果,完整展示了Applet的init、start、paint、stop等生命周期方法的实际应用。不需要额外开发环境,只要系统安装JRE就能在浏览器中打开HTML文件查看动画效果。所有代码带有基础注释,结构清晰,适合Java初学者练习图形绘制、定时刷新和Applet嵌入网页的基本流程,也适用于高校Java课程中关于GUI动画的实践作业参考。
1. 这个“跳动小人”到底是什么?它为什么值得你花时间看懂
如果你在翻找Java入门资料时,偶然看到一个叫“跳动的小人”的压缩包,点开发现里面全是.java、.class、.htm文件,第一反应可能是:“这玩意儿现在还能跑吗?”——别急着关掉。这个看似陈旧的Applet小项目,恰恰是理解Java图形编程底层逻辑的一把钥匙,尤其对刚学完Swing或AWT基础、却还没真正“画”出过动态画面的同学来说,它比任何PPT讲义都管用。
我带过三届Java实训课,每次讲到Graphics绘图和线程动画时,学生最常卡在两个地方:一是“明明调用了repaint(),画面就是不更新”,二是“小人画出来了,但怎么让它动起来?加个Thread.sleep(10)就整个Applet卡死”。而这个“跳动小人”,正是用最朴素的方式,把这两个问题的答案直接写在代码里:它没有用Timer,没碰SwingUtilities.invokeLater,甚至没引入任何第三方库,就靠Applet原生的paint()方法+手动线程控制,实现了稳定、可预测、可调试的周期性动画。它不是教你怎么“写得高级”,而是教你怎么“想得清楚”。
关键词里提到的“Java跳动动画”“Applet小人”“HTML调用Java”,其实指向同一个技术闭环:浏览器作为宿主环境,HTML作为容器胶水,Java Applet作为嵌入式运行时,Graphics作为画笔,Thread作为节拍器。这套组合在2010年代前是Web端轻量交互的标配方案,虽然后来被JavaScript Canvas全面取代,但它的设计思想——分离绘制逻辑与刷新节奏、明确生命周期钩子、用最小资源实现视觉反馈——至今仍是GUI动画开发的底层范式。你不需要真去部署它,但你需要读懂它:boy.java里那几行y += dy; if (y > 300 || y < 50) dy = -dy;,背后是物理运动建模的简化;start()里启动线程、stop()里中断线程的配对操作,体现的是资源生命周期管理的铁律;而boy.html中那一行<applet code="boy.class" width="400" height="400">,则是早期Web-Java桥接最原始的契约。
它适合谁?不是想做现代Web开发的工程师,而是正在啃《Java核心技术卷I》第12章、对着Graphics2DAPI文档发懵的初学者;是需要交一份“能动起来的Applet作业”、又不想抄网上一堆报错代码的学生;是想给培训班学员演示“从零画一个会跳的小人”全过程的讲师。它不炫技,但每行代码都在说人话。接下来,我会带你一层层剥开这个小包——不是照着源码念注释,而是还原当年开发者写这段代码时的真实思考路径:为什么选Applet而不是Frame?为什么用Thread而不是Timer?为什么小人的Y坐标只在50到300之间跳?这些细节,才是你真正能带走的东西。
2. 整体设计思路拆解:为什么用Applet?为什么必须手写线程?
2.1 选择Applet而非独立窗口:教学场景下的精准取舍
这个项目没用JFrame或Frame启动一个独立窗口,而是坚持用Applet嵌入HTML,表面看是“复古”,实则是教学逻辑的必然选择。我们来算一笔账:如果用JFrame,学生要写的代码至少包括:
public class BoyFrame extends JFrame {
public BoyFrame() {
setTitle("跳动小人");
setSize(400, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new BoyPanel()); // 还得额外写一个JPanel子类
setVisible(true);
}
public static void main(String[] args) {
new BoyFrame();
}
}
光是窗口初始化、事件循环、关闭处理,就要绕开核心动画逻辑。而Applet天然省掉了这些——init()对应初始化,start()对应启动,paint()对应绘制,stop()对应暂停,四个方法就是动画生命周期的全部接口。学生打开boy.java,一眼就能看到:
public class boy extends Applet implements Runnable {
Thread t;
int y = 150, dy = 5; // 小人初始Y坐标和速度
public void init() { /* 加载资源、设置参数 */ }
public void start() { /* 启动动画线程 */ }
public void paint(Graphics g) { /* 画小人、画背景 */ }
public void stop() { /* 停止线程 */ }
public void run() { /* 动画主循环 */ }
}
这五块代码,就是GUI动画的“最小可行模型”。没有SwingUtilities的异步调度,没有EventQueue的隐藏队列,所有控制流都在明处。当你调试时,在run()方法里打个断点,就能亲眼看到y值如何一帧帧变化;在paint()里加一行System.out.println("paint called"),就能确认浏览器是否真的在按预期频率调用它。这种“所见即所得”的调试体验,在复杂框架里是奢侈的。
提示:Applet的宿主环境(浏览器)会自动调用
paint(),但不会自动调用run()。run()里的逻辑必须由开发者显式启动的线程执行——这是理解整个设计的关键前提。
2.2 手写Thread而非Timer:暴露底层机制的教学价值
项目里没有用javax.swing.Timer,而是用原始Thread + while(true)循环,这常被初学者视为“过时写法”。但恰恰是这个选择,让动画原理变得透明。我们对比两种实现:
Timer方案(简洁但黑盒):
Timer timer = new Timer(50, e -> {
y += dy;
if (y > 300 || y < 50) dy = -dy;
repaint(); // 触发paint()
});
timer.start();
Thread方案(啰嗦但清晰):
public void start() {
if (t == null) {
t = new Thread(this);
t.start();
}
}
public void run() {
while (true) {
y += dy;
if (y > 300 || y < 50) dy = -dy;
repaint();
try {
Thread.sleep(50); // 主动控制帧间隔
} catch (InterruptedException e) {
break; // stop()会调用interrupt()
}
}
}
区别在哪?Timer把“何时触发”“如何调度”“线程安全”全封装了,学生只看到“每50毫秒执行一次”,却看不到线程是如何被创建、如何被中断、如何与Applet生命周期绑定的。而手写Thread,强制你面对三个核心问题:
- 线程启动时机:必须在
start()里启动,不能在init()里——因为init()只执行一次,而动画需要持续运行; - 线程终止方式:
stop()里必须调用t.interrupt(),否则while(true)会永远卡在sleep()里; - 异常处理意义:
catch(InterruptedException)不是摆设,它是stop()与run()通信的唯一通道。
我让学生做过实验:把Thread.sleep(50)改成Thread.sleep(1000),动画立刻变成“一秒一跳”;把repaint()删掉,小人就定格不动——这些直观反馈,比十页理论文档更能建立“帧率”“重绘”“线程阻塞”的肌肉记忆。
2.3 图形绘制策略:用最简API实现最大表现力
小人图形没用PNG图片,也没用Graphics2D的贝塞尔曲线,而是纯代码绘制:头是圆,身体是矩形,四肢是四条线段。boy.java里的drawBoy(Graphics g)方法只有20行:
void drawBoy(Graphics g) {
// 头
g.fillOval(x-10, y-30, 20, 20);
// 身体
g.fillRect(x-5, y-10, 10, 40);
// 左臂
g.drawLine(x-5, y, x-20, y-15);
// 右臂
g.drawLine(x+5, y, x+20, y-15);
// 左腿
g.drawLine(x-3, y+40, x-15, y+70);
// 右腿
g.drawLine(x+3, y+40, x+15, y+70);
}
这种“手绘”方式有三大教学优势:
- 零资源依赖:不用找图片路径、不用处理透明度、不用考虑加载失败,所有像素由代码定义;
- 参数化控制:
x和y是小人中心坐标,dy是垂直速度,修改任意变量都能立即看到效果; - 理解坐标系:
fillOval(x-10, y-30, 20, 20)中(x-10, y-30)是左上角,不是圆心——这强迫学生真正理解AWT坐标系原点在左上角的设定。
更关键的是,它展示了绘制与状态分离的思想:drawBoy()只负责“画”,不负责“算位置”;位置计算(y += dy)在run()里完成;边界检测(if (y > 300 || y < 50))也独立存在。这种职责划分,正是MVC模式在GUI动画中的雏形。
3. 核心细节解析与实操要点:从源码到可运行的完整链路
3.1 boy.java源码逐行深挖:不只是语法,更是设计意图
我们聚焦boy.java中最容易被忽略的几处细节,它们不是语法糖,而是开发者刻意留下的“教学锚点”。
第一处:x和y的初始化位置
int x = 200, y = 150, dy = 5;
这里x=200固定在画布中央(画布宽400),但y=150不是地面高度,而是小人“脚底”所在Y坐标。注意:drawBoy()里画腿的起点是(x±3, y+40),意味着小人身高约70像素(头20+身40+腿10)。所以当y=150时,小人脚底在190像素处,离画布底部(400)还有210像素空间——这解释了为什么边界检测设为y > 300(脚底触底)和y < 50(头顶撞天花板)。这不是随意写的数字,而是根据图形尺寸反推的物理约束。
第二处:paint()方法里的双缓冲防闪烁
public void paint(Graphics g) {
// 创建临时图像用于双缓冲
if (offscreen == null) {
offscreen = createImage(getSize().width, getSize().height);
buffer = offscreen.getGraphics();
}
// 先画到缓冲区
buffer.setColor(Color.WHITE);
buffer.fillRect(0, 0, getSize().width, getSize().height);
drawBoy(buffer);
// 再一次性拷贝到屏幕
g.drawImage(offscreen, 0, 0, this);
}
很多初学者以为Applet动画闪烁是浏览器问题,其实是paint()直接在屏幕上绘制导致的“撕裂”。这段代码用createImage()创建内存图像,所有绘制先到内存,再用drawImage()整体刷屏——这就是经典的双缓冲技术。虽然现代Swing已内置双缓冲,但在这里,它被显式写出,让你看清“减少闪烁”这件事究竟需要几行代码。
第三处:stop()里的线程安全终止
public void stop() {
if (t != null) {
t.interrupt(); // 发送中断信号
t = null; // 清空引用,避免重复启动
}
}
重点在t.interrupt()。Thread.sleep(50)在等待时收到中断信号,会立即抛出InterruptedException,从而跳出while(true)循环。如果只写t = null而不中断,线程会继续在sleep()里挂起,成为僵尸线程。这个细节,是理解Java线程协作机制的黄金案例。
3.2 HTML调用机制:浏览器如何加载并运行Java字节码
boy.html和跳动的小人.htm本质相同,我们以boy.html为例分析其关键结构:
<html>
<head><title>跳动的小人</title></head>
<body>
<h2>Java Applet跳动小人动画</h2>
<applet code="boy.class" width="400" height="400">
<param name="bgcolor" value="white">
Your browser does not support Java applets.
</applet>
</body>
</html>
这里藏着三个易错点:
-
code="boy.class"的路径规则:boy.class必须与boy.html在同一目录下。如果放在/classes/boy.class,则需写code="classes/boy.class"。很多初学者把.class文件放错位置,浏览器控制台会报ClassNotFoundException,却找不到原因。 -
<param>标签的作用:<param name="bgcolor" value="white">不是设置Applet背景色(那是setBackground()的事),而是向Applet传递初始化参数。虽然本例没在Java代码里读取它,但这是标准传参方式——比如后续扩展“小人颜色”功能,就可以用getParameter("color")获取。 -
降级提示文案:
Your browser does not support Java applets.这段文字至关重要。现代Chrome/Firefox已彻底禁用NPAPI插件,打开此HTML只会显示这句话。这不是代码错误,而是时代变迁的见证。教学时,我会让学生先用旧版IE或JRE自带的Applet Viewer测试,再换Chrome看降级效果——理解“兼容性”不是抽象概念,而是具体到一行HTML标签的生存策略。
3.3 编译与运行环境:JRE版本与安全策略的隐性门槛
项目说明说“只要安装JRE即可”,但实际运行有隐藏条件:
-
JRE版本限制:Java 7u51之后,默认禁止运行未签名的本地Applet。
boy.class没有数字签名,因此在高版本JRE(如Java 8u202+)中会弹出“应用程序已被阻止”的安全警告。 -
解决方案:不是降低JRE版本(不安全),而是配置Java控制面板:
1. 打开控制面板 → Java → 安全选项卡;
2. 将安全级别调至“中”;
3. 在“例外站点列表”中添加file:///(本地文件协议);
4. 重启浏览器。
注意:
file:///必须完整输入,不能只写file://或漏掉末尾斜杠。这是学生最容易填错的地方,填错后仍会报错,但错误信息不提示此处。
另一个常见陷阱是编译编码问题。boy.java若用UTF-8保存(现代编辑器默认),而Windows系统JDK默认用GBK编译,会导致中文注释乱码,进而编译失败。解决方法是在命令行指定编码:
javac -encoding UTF-8 boy.java
或者更稳妥地,用记事本另存为“ANSI”编码(即GBK),再编译——这看似倒退,却是Windows环境下最可靠的方案。
4. 实操过程与核心环节实现:从零开始复现跳动小人
4.1 环境准备:搭建可验证的最小运行环境
不要幻想用IDE一键运行。这个项目的价值在于“脱离IDE看本质”,所以我们用最原始的方式:
步骤1:确认JRE安装
- 命令行输入java -version,确保输出类似java version "1.8.0_202";
- 若提示“不是内部命令”,需下载JRE并配置PATH环境变量(官网下载jre-8u202-windows-x64.exe,安装时勾选“添加到PATH”)。
步骤2:准备纯净工作目录
- 新建文件夹D:\boy-applet;
- 将boy.java、boy.html、说明.txt复制进去;
- 删除其他所有文件(如.gitignore、www.pudn.com.txt等无关文件),避免干扰。
步骤3:手动编译(关键!)
- 打开命令行,cd D:\boy-applet;
- 执行javac boy.java;
- 成功后应生成boy.class文件(约3KB大小);
- 验证编译结果:用文本编辑器打开boy.class,开头应是CAFEBABE(Java字节码魔数),证明编译成功。
提示:如果报错
error: Class names, 'boy.java', are only accepted if annotation processing is explicitly requested,说明你误用了java boy.java(Java 11+支持直接运行.java文件,但会跳过编译步骤,导致Applet无法加载)。必须用javac编译。
4.2 运行调试:用Applet Viewer绕过浏览器限制
现代浏览器禁用Applet,但我们有JDK自带的appletviewer工具:
步骤1:启动Applet Viewer
- 命令行执行appletviewer boy.html;
- 正常应弹出独立窗口,显示白色背景上的跳动小人。
步骤2:注入调试日志
- 在boy.java的run()方法开头添加:java System.out.println("Frame " + frameCount + ": y=" + y + ", dy=" + dy); frameCount++;
- 重新编译运行,命令行会实时打印坐标变化,验证动画逻辑是否按预期执行。
步骤3:模拟边界碰撞
- 临时修改run()里的边界条件:java if (y > 200 || y < 100) dy = -dy; // 缩小活动范围
- 观察小人是否在更窄区域内高频弹跳——这证明物理模型正确,且dy符号翻转生效。
4.3 HTML页面定制:两个文件的区别与使用场景
项目提供boy.html和跳动的小人.htm,表面看只是文件名不同,实则体现两种教学意图:
boy.html:极简结构,仅包含<applet>标签和降级提示,适合讲解“Applet嵌入原理”;跳动的小人.htm:包含完整HTML5声明、CSS样式、中文标题和说明文字,适合直接作为课程作业提交——它展示了“如何把技术实现包装成可用产品”。
你可以动手改造boy.html,加入以下增强功能:
添加速度控制滑块:
<input type="range" min="10" max="100" value="50" id="speed">
<script>
document.getElementById('speed').oninput = function() {
// 需要在boy.class中暴露setSpeed()方法,此处仅为示意
// 实际需修改Java代码,通过JSObject与Applet通信
}
</script>
添加暂停/播放按钮:
<button onclick="document.applets[0].pause()">暂停</button>
<button onclick="document.applets[0].resume()">播放</button>
(需在boy.java中添加pause()/resume()方法,并用synchronized保护dy变量)
这些扩展不是必须的,但它们揭示了一个事实:Applet虽老,但它的“Java逻辑+HTML界面”架构,正是现代Web组件化思想的远古回响。
5. 常见问题与排查技巧实录:那些年踩过的坑
5.1 经典报错速查表
| 错误现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 浏览器显示“应用程序已被阻止” | JRE安全策略拦截 | 检查Java控制面板安全级别 | 将安全级别设为“中”,添加file:///到例外站点 |
Applet Viewer报错java.lang.ClassNotFoundException: boy |
.class文件路径错误 |
确认boy.class与boy.html同目录 |
移动boy.class到HTML同级目录,或修改code属性 |
| 小人不跳动,只显示静态图形 | start()未被调用或线程未启动 |
在start()开头加System.out.println("start called") |
确保HTML中<applet>标签闭合正确,无语法错误 |
| 小人跳动但闪烁严重 | 缺少双缓冲 | 检查paint()中是否有createImage()逻辑 |
确认offscreen和buffer变量已初始化,绘制目标是buffer而非g |
编译时报非法字符:\65279 |
文件BOM头导致 | 用Notepad++打开boy.java,编码→转为ANSI |
保存后重新编译 |
5.2 独家避坑技巧:来自十年教学实战
技巧1:用“时间戳差值”替代Thread.sleep()Thread.sleep(50)看似简单,但实际帧率不稳定(系统调度延迟)。更精确的做法是记录上帧时间:
long lastTime = System.currentTimeMillis();
while (true) {
long now = System.currentTimeMillis();
if (now - lastTime >= 50) { // 确保至少间隔50ms
y += dy;
if (y > 300 || y < 50) dy = -dy;
repaint();
lastTime = now;
}
Thread.yield(); // 让出CPU,避免空转耗电
}
这招在学生做课程设计时屡试不爽,尤其当电脑性能较差时,能保证动画节奏不崩。
技巧2:paint()里绝不做耗时操作
曾有学生在paint()里加了FileReader读配置文件,结果动画卡成幻灯片。记住铁律:paint()是高频回调,必须在毫秒级完成。所有数据预处理(如读取图片、计算路径)必须在init()里做完,paint()只负责“画”。
技巧3:用getBounds()替代硬编码尺寸boy.html里写死width="400" height="400",但Applet代码里用getSize().width获取尺寸。这样即使改HTML尺寸,Java端自动适配。我在课堂上会让学生把HTML改成width="600",观察小人是否自动居中——这比讲一百遍“响应式设计”更有说服力。
5.3 从Applet到现代实践:如何把这份经验迁移到今天
这个小项目不是怀旧标本,而是能力迁移的跳板。我把学生后续能做的三个延伸方向列出来:
方向1:迁移到Swing
- 删除extends Applet,改为extends JPanel;
- 把paint()重写为paintComponent(Graphics g);
- 用Timer替代手动线程;
- main()方法启动JFrame。
收获:掌握Swing事件线程与绘制线程的分离原则。
方向2:迁移到JavaFX
- 用Canvas替代Graphics;
- 用AnimationTimer替代Thread;
- 小人用Path或Shape绘制,支持平滑缩放。
收获:理解硬件加速渲染与CPU渲染的本质差异。
方向3:逆向工程思维
- 用JD-GUI反编译boy.class,对比源码与字节码;
- 修改dy字段为public,用反射在HTML里动态修改:document.applets[0].dy = 10;;
收获:建立“代码→字节码→运行时”的全链路认知。
最后分享一个小技巧:把这个跳动小人当作你的Java“Hello World”进阶版。每次学到新知识(比如SwingWorker、CompletableFuture、甚至Kotlin协程),都试着用它来验证——不是为了做出更好看的动画,而是为了确认自己真的理解了那个技术点在GUI上下文中的行为。真正的学习,从来不是记住API,而是亲手把它拧进一个会跳的小人身上。
简介:这个资源包提供一个可直接运行的Java跳动小人动画实现,核心是基于Applet的轻量级图形动画程序。里面包含boy.java源代码文件,已编译好的boy.class字节码,两个HTML调用页面(boy.html和跳动的小人.htm),一份纯文本说明文档(说明.txt),以及Word格式的操作指引(boy_html.doc)。整个动画通过Java绘图API绘制小人图形,利用线程控制实现周期性上下跳动效果,完整展示了Applet的init、start、paint、stop等生命周期方法的实际应用。不需要额外开发环境,只要系统安装JRE就能在浏览器中打开HTML文件查看动画效果。所有代码带有基础注释,结构清晰,适合Java初学者练习图形绘制、定时刷新和Applet嵌入网页的基本流程,也适用于高校Java课程中关于GUI动画的实践作业参考。
更多推荐




所有评论(0)