怀念经典,一步步开发一个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" />
  • tagbutton:普通按钮,无记忆状态。
  • tagtoggle:开关按钮,对应代码中的 selected 属性,点击后状态常亮。

TtButton 虽然不直接解析 XML,但 SkinWindow 或上层 UI 构建器会根据 Ctl 对象的属性(image 指向的 BMP,以及 tag 是否为 toggle)来实例化 TtButton,并调用 setSelected() 方法。


第四部分:总结

TtButton 通过 4 帧水平大图 实现了状态切换,利用 selected 字段优雅地支持了自锁开关(Toggle)功能。在千千静听中,按钮种类丰富,覆盖了 播放控制、窗口管理、音效调节、列表操作 四大功能区。理解了这个组件的绘制逻辑,就能完全掌握 TTPlayer 皮肤中所有按钮的渲染与交互原理——它们本质上都是同一套 TtButton 类,只是切换了不同的图片资源而已。

License

本项目仅供学习和个人使用。皮肤资源版权归原作者所有。

「获取方式」:后台回复 ttplayer

image

posted @ 2026-08-28 10:56  tomj2ee  阅读(2)  评论(0)    收藏  举报