怀念经典,一步步开发一个ttplayer千千静听,按钮绘制
好的,我们先来深度解析 TtButton 这个类的源码实现,然后结合千千静听(TTPlayer)经典的皮肤 UI 布局,为你详细列举它到底有哪些按钮。
第一部分:TtButton 源码深度解析
这个类继承自 JComponent,是一个自定义轻量级按钮组件,它完全使用图像绘制,不依赖 Swing 原生 JButton。
1. 核心数据结构
public enum State { NORMAL, HOVER, PRESSED, DISABLED }
private final BufferedImage[] frames = new BufferedImage[4];
private State state = State.NORMAL;
private boolean selected = false; // 选中驻留态(用于启用开关)
private boolean rollover; // 鼠标是否悬停
private final List<ActionListener> listeners = new ArrayList<>();
- 4帧设计:按钮的 4 种状态(正常、悬停、按下、禁用)全部存储在一张 横向拼接的大图中。
frames[0]=正常,frames[1]=悬停,frames[2]=按下,frames[3]=禁用。 selected状态:这是一个切换开关标志。如果selected=true,即使鼠标没有按下,按钮也会强制显示“按下”帧(PRESSED),用于表示功能已开启(如“循环播放”点亮)。
2. 构造方法(图像解析与切分)
public TtButton(byte[] bmpData, int left, int top, int right, int bottom, Color transp) {
BufferedImage src = SkinWindow.decodeBmp(bmpData, transp);
if (src != null) {
int frameW = src.getWidth() / 4; // 关键:宽度必须正好是4倍帧宽
int frameH = src.getHeight();
for (int i = 0; i < 4; i++) {
frames[i] = src.getSubimage(i * frameW, 0, frameW, frameH);
}
setPreferredSize(new Dimension(frameW, frameH));
} else {
// 降级方案:如果没有图片,使用传入的坐标作为尺寸占位
setPreferredSize(new Dimension(right - left, bottom - top));
}
// ... 绑定鼠标监听器
}
- 硬编码假设:代码强制要求图片宽度 恰好是单帧宽度的 4 倍。这与千千静听皮肤规范完全一致——制作者必须将 4 个状态水平排列。
- 透明色处理:通过
SkinWindow.decodeBmp将 BMP 中的洋红(#FF00FF)像素转为真正的透明,实现异形按钮(不规则形状)。
3. 鼠标事件交互(行为逻辑)
addMouseListener(new MouseAdapter() {
@Override public void mouseEntered(MouseEvent e) {
rollover = true;
if (enabled) { state = State.HOVER; repaint(); }
}
@Override public void mouseExited(MouseEvent e) {
rollover = false;
if (enabled) { state = State.NORMAL; repaint(); }
}
@Override public void mousePressed(MouseEvent e) {
if (enabled) { state = State.PRESSED; repaint(); }
}
@Override public void mouseReleased(MouseEvent e) {
if (enabled) {
state = rollover ? State.HOVER : State.NORMAL;
repaint();
if (rollover) fireActionPerformed(); // 只有松开时鼠标还在按钮上才触发点击
}
}
});
- 防误触设计:
mouseReleased中判断if (rollover),即只有鼠标在按钮区域内松开才触发点击事件,防止按住拖出按钮外后松开的无效操作。
4. 绘制逻辑(paintComponent)
protected void paintComponent(Graphics g) {
BufferedImage frame = frames[selected && enabled ? State.PRESSED.ordinal() : state.ordinal()];
if (frame != null) {
g.drawImage(frame, 0, 0, getWidth(), getHeight(), null);
}
}
- 优先级覆盖:
selected的优先级高于当前鼠标状态。即使鼠标悬停(HOVER),只要selected=true,依然绘制按下帧。这完美实现了“开关灯”效果(如均衡器开启按钮常亮)。
第二部分:TTPlayer(千千静听)到底有哪些按钮?
千千静听的经典皮肤 UI 由多个独立窗口组成,每个窗口都包含大量使用 TtButton 的控件。根据官方皮肤 XML(Skin.xml)规范和经典布局,按钮主要分布在以下区域:
1. 主窗口(Main Window)—— 播放控制核心
这是按钮最集中的地方,位于播放进度条上方。
| 按钮名称 | 功能描述 | 是否为开关(Selected) | 备注 |
|---|---|---|---|
| 播放/暂停 | 播放或暂停当前曲目 | ✅ 是(暂停时点亮) | 图标通常为 ▶ / ⏸ |
| 停止 | 停止播放 | ❌ 否 | 图标 ■ |
| 上一首 | 切换到上一曲目 | ❌ 否 | 图标 ⏮ |
| 下一首 | 切换到下一曲目 | ❌ 否 | 图标 ⏭ |
| 打开文件 | 弹出文件选择对话框 | ❌ 否 | 图标 📂 |
| 弹出菜单 | 显示右键菜单 | ❌ 否 | 通常位于左上角图标 |
| 最小化 | 窗口最小化到任务栏 | ❌ 否 | 图标 ─ |
| 最大化/还原 | 切换窗口大小 | ✅ 是(还原时变回正常) | 图标 □ / □□ |
| 关闭 | 退出程序 | ❌ 否 | 图标 ✕ |
| 播放模式(循环) | 切换播放顺序(顺序/随机/单曲循环) | ✅ 是(开启时点亮) | 图标 🔄 |
| 歌词显示 | 显示/隐藏歌词秀窗口 | ✅ 是 | 图标 🎤 |
| 均衡器 | 显示/隐藏均衡器窗口 | ✅ 是 | 图标 🎛️ |
| 播放列表 | 显示/隐藏播放列表窗口 | ✅ 是 | 图标 📋 |
2. 均衡器窗口(Equalizer Window)
位于主窗口下方或独立弹出。
| 按钮名称 | 功能描述 | 是否为开关 |
|---|---|---|
| 开启均衡器 | 启用/禁用 EQ 效果 | ✅ 是(点亮表示启用) |
| 预设 | 加载预设音效配置 | ❌ 否 |
| 复位 | 将所有频段滑块归零 | ❌ 否 |
| 自动 | 自动平衡(AGC) | ✅ 是 |
3. 播放列表窗口(Playlist Window)
位于主窗口右侧或独立弹出。
| 按钮名称 | 功能描述 | 是否为开关 |
|---|---|---|
| 添加文件 | 添加歌曲到列表 | ❌ 否 |
| 添加文件夹 | 批量添加目录 | ❌ 否 |
| 删除选中 | 移除选中的歌曲 | ❌ 否 |
| 清空列表 | 移除所有歌曲 | ❌ 否 |
| 列表循环 | 切换列表播放模式 | ✅ 是 |
| 保存列表 | 导出为 .m3u / .pls |
❌ 否 |
4. 歌词秀窗口(Lyric Window)
- 锁定歌词:禁止鼠标拖动歌词位置(✅ 开关)。
- 滚动模式:切换滚动方式(✅ 开关)。
第三部分:TtButton 与皮肤文件(.skn)的映射关系
在之前的 TtSkin 解析中,Ctl 对象里有一个 tag 字段。在官方皮肤 Skin.xml 中,这些按钮通过标签区分:
<!-- 主窗口中的播放按钮 -->
<button position="30,10,60,30" image="btn_play.bmp" />
<!-- 循环模式开关(选中态驻留) -->
<toggle position="80,10,110,30" image="btn_mode.bmp" />
tag为button:普通按钮,无记忆状态。tag为toggle:开关按钮,对应代码中的selected属性,点击后状态常亮。
TtButton 虽然不直接解析 XML,但 SkinWindow 或上层 UI 构建器会根据 Ctl 对象的属性(image 指向的 BMP,以及 tag 是否为 toggle)来实例化 TtButton,并调用 setSelected() 方法。
第四部分:总结
TtButton 通过 4 帧水平大图 实现了状态切换,利用 selected 字段优雅地支持了自锁开关(Toggle)功能。在千千静听中,按钮种类丰富,覆盖了 播放控制、窗口管理、音效调节、列表操作 四大功能区。理解了这个组件的绘制逻辑,就能完全掌握 TTPlayer 皮肤中所有按钮的渲染与交互原理——它们本质上都是同一套 TtButton 类,只是切换了不同的图片资源而已。
License
本项目仅供学习和个人使用。皮肤资源版权归原作者所有。
「获取方式」:后台回复 ttplayer


浙公网安备 33010602011771号