怀念经典,一步步开发一个ttplayer千千静听,led绘制
我们先从千千静听(TTPlayer)的 LED 显示机制讲起,包括它在皮肤 XML 中的定义方式,然后对 TtLed 这个类的源码进行逐行、深度剖析。

第一部分:千千静听的 LED 显示是什么?
1. 视觉风格与用途
在千千静听中,LED 显示指的是主窗口中间那块模拟老式数码管(VFD 荧光显示或 LED 点阵)的区域,主要用于:
- 播放时间(当前播放位置 / 总时长),格式通常为
MM:SS(如03:45)或HH:MM:SS。 - 频谱指示(某些皮肤下显示跳动的柱状图,但那是另一个控件
TtEq,不是TtLed)。
它的视觉特点是字符与字符之间保持固定间距,背景全黑或透明,字符颜色鲜艳(青绿、橙红等),极具怀旧感。
2. 皮肤文件(.skn)中的定义 —— XML 配置
在 Skin.xml 文件中,LED 控件通过 <led> 标签定义,TtSkin 解析器会专门处理它。
典型 XML 片段:
<main_window position="0,0,275,116" image="main.bmp">
<!-- 时间显示 LED -->
<led position="95,72,155,88" image="led_time.bmp" />
<!-- 或者包含冒号等符号的宽LED -->
</main_window>
TtSkin 解析 led 时的特殊逻辑(参考你之前发的 TtSkin.java):
if ("led".equals(btn.tag)) {
btn.cols = 12; // 强制 12 列
btn.frameWidth = iw / 12; // 每帧宽度 = 总宽 / 12
btn.frameHeight = ih; // 高度不变
}
- 硬编码 12 列:因为 LED 图片必须包含 10 个数字(0-9)+ 冒号(:)+ 短横(-),恰好 12 个符号水平排列。
第二部分:TtLed 源码完整深度剖析
1. 类结构概览
public class TtLed extends javax.swing.JComponent {
private BufferedImage[] frames = new BufferedImage[12];
private String text = "00:00";
private boolean alignRight = true; // 右对齐,默认 true
// ...
}
- 继承自
JComponent,是一个轻量级 Swing 组件。 frames数组:存储 12 帧图像(索引 0~9 对应字符 '0'~'9',10 对应 ':',11 对应 '-')。text:要显示的字符串,默认"00:00"(5 个字符)。alignRight:是否右对齐。如果为true,当显示内容不足 6 个字符时,字符靠右显示(模拟倒计时或时间右对齐效果)。
2. 构造方法(图像加载与切分)
2.1 重载构造
public TtLed(byte[] bmpData) {
this(bmpData, new Color(255, 0, 255));
}
public TtLed(byte[] bmpData, Color transp) {
// ... 核心逻辑
}
- 默认透明色为洋红
#FF00FF,这是 TTPlayer 皮肤通用的透明标记色。
2.2 核心构造逻辑
BufferedImage src = SkinWindow.decodeBmp(bmpData, transp);
if (src != null) {
int fw = src.getWidth() / 12; // ⭐ 关键:宽度必须是 12 的整数倍
int fh = src.getHeight();
for (int i = 0; i < 12; i++) {
frames[i] = src.getSubimage(i * fw, 0, fw, fh);
}
// 设置首选大小为6个字符宽度
setPreferredSize(new Dimension(fw * 6, fh));
}
setOpaque(false);
- 图像切分:从
src中水平切出 12 等份,每份作为一帧。 fw * 6:虽然标准时间MM:SS只有 5 个字符(如12:34),但为了预留安全余量或支持-MM:SS(负号倒计时),这里固定给定了 6 个字符 的默认宽度。这也与paintComponent中的len = Math.min(display.length(), 6)相呼应。setOpaque(false):设置背景透明,让皮肤的背景图片透出来。
3. setText(String t)
public void setText(String t) {
this.text = (t != null) ? t : "00:00";
repaint();
}
- 外部(如播放器内核)通过此方法更新时间字符串。
- 传入
null时兜底为"00:00"。 - 调用
repaint()触发重绘。
4. paintComponent(Graphics g) —— 核心绘制逻辑
这是整个类最复杂的部分,我们逐段分解。
4.1 空帧检查与尺寸获取
if (frames[0] == null) return;
int w = getWidth();
int h = getHeight();
int fw = frames[0].getWidth();
int fh = frames[0].getHeight();
- 如果没有加载到图片,直接返回(避免
NullPointerException)。
4.2 字符串截取与字符数量计算
String display = text;
int len = Math.min(display.length(), 6);
- 最多显示 6 个字符。如果传入
"123:45"(6个字符)显示完整;如果传入"12:34:56"(8个字符),只会显示前 6 个"12:34:"(但通常播放器不会传超过 6 个字符给这个控件)。
4.3 动态计算字符宽度(自适应缩放)
int actualCharWidth = fw;
if (len > 0) {
actualCharWidth = Math.min(fw, w / len);
}
int totalW = len * actualCharWidth;
- 设计意图:当组件的实际宽度小于原始图片宽度时,按比例压缩每个字符,确保所有字符都能完整显示在控件内。
Math.min(fw, w / len):取“原始帧宽”和“可用宽度/字符数”中的较小值。如果控件宽度很宽,也不会超过原始帧宽(避免放大导致锯齿)。totalW:所有字符占据的总宽度。
4.4 对齐计算(默认右对齐)
int startX = 0;
if (alignRight) {
if (totalW <= w) {
startX = w - totalW;
}
}
int y = (h - fh) / 2; // 垂直居中
- 右对齐逻辑:如果总宽度小于控件宽度,将起点定在
(宽度 - 总宽)处,字符向右靠拢。 - 垂直居中:将帧绘制在控件垂直中央。
4.5 字符映射与逐帧绘制
for (int i = 0; i < len; i++) {
char c = display.charAt(i);
int idx;
if (c >= '0' && c <= '9') idx = c - '0';
else if (c == ':') idx = 10;
else if (c == '-') idx = 11;
else continue; // 不支持其他字符,跳过
if (idx >= 0 && idx < 12 && frames[idx] != null) {
int drawX = startX + i * actualCharWidth;
g.drawImage(frames[idx], drawX, y, actualCharWidth, fh, null);
}
}
- 映射表:
'0'~'9'→ 索引0~9':'→ 索引10'-'→ 索引11
- 不支持空格、字母等,直接跳过(不绘制)。
- 绘制:调用
drawImage将帧拉伸到(actualCharWidth, fh)。注意这里垂直方向没有拉伸,保持了原始高度,只有宽度动态变化。
5. 对齐控制 Setter
public void setAlignRight(boolean r) { alignRight = r; }
- 允许外部动态切换对齐方式(某些皮肤可能希望左对齐)。
第三部分:
✅ 设计亮点
- 高度复刻经典:12 帧精灵图完美模拟了老式数码管显示,怀旧感十足。
- 自适应宽度:通过动态计算
actualCharWidth,组件可以缩放而不变形(仅水平拉伸字符),适合不同 DPI 或窗口缩放。 - 默认右对齐:符合音乐播放器时间显示的习惯(时间向右对齐,便于快速定位时长)。
第四部分:与 TtSkin / TtButton 的联动关系
| 类 | 图片切分方式 | 帧数 | 用途 |
|---|---|---|---|
TtButton |
宽度 / 4 | 4 帧 | 正常、悬停、按下、禁用 |
TtLed |
宽度 / 12 | 12 帧 | 0-9、冒号、短横 |
两者都依赖 SkinWindow.decodeBmp 解码 BMP,并遵循相同的透明色约定。在 UI 构建时,TtSkin 解析出 <led> 标签,将 image 属性指向的图片数据传给 TtLed 构造函数,完成实例化。
总结
TtLed 是一个专精于数字符号显示的轻量组件,它通过 12 帧精灵图 + 字符映射,完美还原了千千静听标志性的数码管时间显示。
License
本项目仅供学习和个人使用。皮肤资源版权归原作者所有。
「获取方式」:后台回复 ttplayer


浙公网安备 33010602011771号