怀念经典,一步步开发一个ttplayer千千静听,开篇skin皮肤解析

第一乐章深度拆解:当Swing遇上千千静听真实皮肤XML —— 从官方Skin到JVM像素级还原

拆开一个.skin文件,里面藏着的是一份完整的UI蓝图;我们用Java Swing把它变成可交互的播放器。

这份XML定义了多达8个独立窗口/面板:主播放器、歌词秀、均衡器、播放列表、迷你模式、浏览器、移动设备面板、桌面歌词条。每一个都承载着不同的交互场景。下面,我们就按窗口逐一解剖。


一、皮肤头信息与全局约定

<skin version="2" name="TTP Classic" author="TTplayer" 
      url="http://www.ttplayer.com" email="none" 
      transparent_color="#ff00ff">
  • transparent_color="#ff00ff"透明色键(类似绿幕抠像)。在绘制时,所有图片中该颜色的像素会被视为透明。Swing中,我们可以在ImageIO读取后遍历像素,将匹配的颜色替换为全透明(Alpha=0)的ARGB值,从而实现不规则窗口。

二、主播放器窗口(player_window)—— 核心控制中枢

这是整个播放器最复杂的部分,定义了几乎所有的交互控件。

<player_window image="player_skin.bmp">
  • image 指定背景图片,整个窗口的底图。

1. 传输控制按钮(播放/暂停/上一首/下一首)

<play position="54, 110, 96, 152" image="play.bmp"/>
<pause position="54, 110, 96, 152" image="pause.bmp"/>
<prev position="8, 110, 50, 152" image="prev.bmp"/>
<next position="100, 110, 142, 152" image="next.bmp"/>
  • position 格式为 left, top, right, bottom,即矩形区域。
  • playpause 坐标相同,说明它们共用同一块热区,只是根据播放状态切换显示的图片(image)。
  • Swing实现:创建一个ToggleButtonImagePanel,监听点击事件,根据状态切换image字段,并调用repaint()

2. 播放模式切换(单曲/循环/顺序/随机)

<mode_single position="146, 110, 188, 152" image="mode_01.bmp"/>
<mode_loop position="146, 110, 188, 152" image="mode_02.bmp"/>
<mode_slider position="146, 110, 188, 152" image="mode_03.bmp"/>
<mode_circle position="146, 110, 188, 152" image="mode_04.bmp"/>
<mode_random position="146, 110, 188, 152" image="mode_05.bmp"/>
  • 它们共享同一个热区,通过点击循环切换不同的模式图标。Swing中可以用一个JButton,点击时轮换image并更新播放器模式变量。

3. 功能面板切换按钮(歌词/均衡器/播放列表/浏览器)

<lyric position="240, 29, 272, 50" image="lyric.bmp"/>
<equalizer position="208, 29, 240, 50" image="equalizer.bmp"/>
<playlist position="174, 29, 208, 50" image="playlist.bmp"/>
<browser position="272, 29, 315, 50" image="browser.bmp" />
  • 点击这些按钮会显示/隐藏对应的子窗口(通常为JDialog或独立JFrame)。

4. 窗口控制(最小化/迷你模式/退出)

<minimize position="230, 3, 257, 23" image="minimize.bmp"/>
<minimode position="257, 3, 288, 23" image="minimode.bmp"/>
<exit position="288, 3, 316, 23" image="close.bmp"/>
  • minimode 是切换到迷你模式(mini_window)的按钮。

5. 进度条(Progress)与音量条(Volume)

<progress position="16, 93, 312, 106" bar_image="" 
          thumb_image="progress_thumb.bmp" 
          fill_image="progress_fill.bmp" 
          fill_image2="progress_fill2.bmp"/>
  • bar_image 为空,表示背景轨道直接用背景图里画好的部分;thumb_image 是滑块;fill_imagefill_image2 可能用于左右声道或双色填充(如已缓冲和已播放)。
  • Swing实现:自定义Slider组件,计算鼠标X相对坐标比例,绘制轨道、填充区和滑块。
<volume position="234, 124, 309, 139" vertical="false" 
        thumb_image="v_thumb.bmp" fill_image="progress2.bmp"/>
  • vertical="false" 表示水平滑动。

6. 信息显示(歌名/时间)

<info position="17, 34, 170, 48" color="#FFFFFF" font="Tahoma" font_size="13" align="left"/>
<led position="176, 65, 308, 82" image="number.bmp" align="right"/>
  • info 显示歌曲标题、艺术家等文本,直接使用Graphics.drawString
  • led 是时间数字显示,用数字图片拼合(类似数码管)。image="number.bmp" 是一张包含0-9数字和冒号的位图,需要根据当前时间(mm:ss)截取对应数字区域绘制。

7. 频谱可视化区域

<visual position="17, 56, 167, 87"/>
  • 没有image属性,说明它是动态绘制的区域,用于绘制FFT频谱柱状图。我们在此区域进行实时频谱渲染。

8. 静音按钮、设置按钮、图标

<mute position="217, 125, 230, 138" image="mute.bmp" />
<set position="199, 3, 226, 23" image="set.bmp" />
<icon position="7, 5, 23, 21" icon="TTPlayer.ico"/>
  • icon 用于设置窗口图标(setIconImage)。

三、歌词秀窗口(lyric_window)—— 让歌词“飞”起来

<lyric_window position="0, 381, 327, 503" resize_rect="69, 34, 214, 101" image="lyric_skin.bmp">
  • position 定义窗口默认位置和大小(x,y,width,height),resize_rect 指定可拖拽调整大小的边缘区域(类似Windows的WM_NCHITTEST热区)。
  • image 是窗口背景。
<lyric position="16, 32, 312, 106"/>
  • 歌词显示区域,实际绘制文本(双行或逐字染色)的地方。
<close position="288, 3, 316, 23" image="close3.bmp" align="right"/>
<ontop position="257, 3, 288, 23" image="ontop.bmp" align="right"/>
<desklrc position="230, 3, 257, 23" image="desklrc.bmp" align="right"/>
  • ontop 是“窗口置顶”按钮,desklrc 是“桌面歌词”按钮(点击后显示desklrc_bar)。

四、均衡器窗口(equalizer_window)—— 十段均衡的精准布局

<equalizer_window position="0, 503, 327, 602" image="eq_skin.bmp" eq_interval="5">
<close position="289, 3, 316, 23" image="close2.bmp" align="right"/>
<enabled position="199, 3, 226, 23" image="eq_enabled.bmp"/>
<profile position="257, 3, 285, 23" image="eq_profile.bmp"/>
<reset position="226, 3, 257, 23" image="reset.bmp"/>
  • enabled 开关均衡效果;profile 可能打开预设菜单;reset 重置所有频段到0dB。
<balance position="19, 36, 71, 49" bar_image="" fill_image="eqfactor_full2.bmp" thumb_image="eqfactor_thumb2.bmp"/>
<surround position="19, 60, 71, 73" bar_image=""  fill_image="eqfactor_full2.bmp" thumb_image="eqfactor_thumb2.bmp"/>
<preamp position="92, 32, 105, 84" bar_image=""  fill_image="eqfactor_full.bmp" thumb_image="eqfactor_thumb.bmp"/>
<eqfactor position="131, 32, 144, 84" bar_image="" fill_image="eqfactor_full.bmp" thumb_image="eqfactor_thumb.bmp"/>
  • balancesurround 是平衡和环绕调节滑块(范围较小)。
  • preamp 是前置增益。
  • 重点eqfactor 只有一个节点,但它代表一组频段滑块。eq_interval="5" 属性暗示了滑块之间的间隔像素,通过此信息,我们需要在代码中复制10个(或更多)滑块,并依次计算它们的X坐标。例如,起始X=131,间隔5,则第i个滑块的X = 131 + i * (滑块宽度 + 5),宽度由eqfactor的矩形宽度推算(144-131=13)。

五、播放列表窗口(playlist_window)—— 歌曲管理

<playlist_window position="0, 157, 327, 381" resize_rect="77, 50, 258, 102" image="playlist_skin.bmp">
<close position="289, 3, 316, 23" image="close2.bmp" align="right"/>
<toolbar position="23, 26, 303, 47" image="playlist_toolbar.bmp" hot_image="playlist_toolbar_hot.bmp" align="left"/>
  • toolbar 是顶部工具栏图片,包含添加、删除、清空等按钮的热区?实际上这个toolbar本身是一整张图片,但它的点击热区需要进一步拆分。在原版实现中,可能通过判断鼠标点击在工具栏图片上的相对位置来区分不同功能。我们也可以采取类似策略:将工具栏图片划分为若干个隐式按钮,或者干脆在XML里不细分,而是用硬编码的坐标映射。
<scrollbar buttons_image="scrollbar_button.bmp" thumb_image="scrollbar_thumb.bmp" bar_image="scrollbar_bar.bmp" align="center"/>
  • 自定义滚动条组件,替代Swing默认的JScrollBar
<playlist position="11, 48, 316, 112" selected_image="playlist_selected.bmp"/>
  • 列表显示区域,selected_image 用于高亮当前选中行(背景图)。
<static_tip position="90, 100, 300, 150" color="#8BBAC6" bkgnd="#FFFFFF" font="宋体" font_size="12" align="center"/>
  • 当列表为空时显示的提示文字(“添加歌曲”等)。

六、迷你模式窗口(mini_window)—— 小巧精悍

<mini_window image="mini-player.bmp">
  • 迷你模式是一个简化版主窗口,通常只有控制按钮和进度条。
<play position="261, 5, 282, 21" image="play_mini.bmp" />
<pause position="261, 5, 282, 21" image="pause_mini.bmp" />		
<prev position="236, 5, 257, 21" image="prev_mini.bmp" />
<next position="286, 5, 307, 21" image="next_mini.bmp" />
<progress position="129, 8, 224, 18" bar_image="" thumb_image="progress_thumb2.bmp" fill_image="progress_fill_mini.bmp" fill_image2="progress_fill_mini2.bmp"/>
<lyric position="376, 1, 382, 24" image="lyric_mini.bmp" />
<minimode position="336, 5, 353, 21" image="minimode_mini.bmp" />
<minimize position="316, 5, 333, 21" image="minimize_mini.bmp" />
<exit position="356, 5, 373, 21" image="close_mini.bmp" />
<info position="13, 6, 120, 19" color="#FFFFFF" font="Tahoma" font_size="13" align="left"/>
<icon position="-300, -300, -300, -300" image="ttplayer.ico"/>
  • 注意icon的位置为负值,表示不绘制(可能隐藏图标)。
  • minimode 按钮点击后切回主窗口。

七、浏览器窗口(browser_window)—— 内置网页或音乐商店

<browser_window position="327, 0, 897, 602" image="browser_skin.bmp" transparent_color="#ff00ff">
  • 这个窗口原本用于显示内置浏览器(如音乐商店)。在Swing中,我们可以嵌入JEditorPaneWebView(JavaFX)。
<close position="531, 3, 558, 23" image="close2.bmp" />
<minibtn position="109,4,165,23" image="browser_mini.bmp" />
<downloadbtn position="173,4,229,23" image="browser_download.bmp"/>
<backward position="13, 3, 40, 23" image="browser_backward.bmp" />
<forward position="40, 3, 71, 23" image="browser_forward.bmp" />
<refresh position="71, 3, 99, 23" image="browser_refresh.bmp" />
<startup position="11, 581, 115, 594" ckbox_image="browser_startup.bmp" interval=4 color="#5C7C9C" font="SimSun" font_size="12" />
<linktxt position="300, 579, 563, 595" color="#5C7C9C" font="SimSun" font_size="12" />
<browser position="8, 29, 562, 571" />
  • browser节点是浏览器主体区域,我们将放置JEditorPaneJFXPanel
  • startup是一个复选框(ckbox_image),用于“启动时打开浏览器”。

八、移动设备面板(mobile_window)—— 同步歌曲到便携设备

<mobile_window position="897, 0, 1012, 602" image="mobile_skin.bmp">
  • 这个窗口用于管理MP3等设备的文件传输,非常经典。

内部节点较多,列举关键:

  • dirdirshowdirspace 显示设备目录和剩余空间。
  • copylisttitlecopylisttitlebk 标题和背景。
  • listinfo 文件列表区域,包含行高、颜色等属性。
  • importimportokimportfinishstopimport 是复制进度相关的按钮。
  • progressprogresstextcount 显示复制进度和计数。
  • picture 节点引用了各种状态图标(删除、完成、错误等)。

Swing实现:这个窗口涉及文件IO和进度更新,适合用SwingWorker在后台执行复制任务,并更新进度条和文本。


九、桌面歌词条(desklrc_bar)—— 悬浮在屏幕上的歌词

<desklrc_bar image="desklrc_bar.bmp" transparent_color="#ff00ff">
  • 这是一个横条工具栏,包含控制按钮和歌词显示?实际上desklrc_bar本身可能只是一个控制条,真正的桌面歌词是独立的浮动窗口。但在这个XML里,desklrc_bar只定义了按钮,没有歌词显示区域。或许桌面歌词是通过另一个机制实现的(比如lyric_windowdesklrc按钮切换)。

控件列表:

  • play/pause/prev/next 等基本控制。
  • listsettingskalaoklineslockontopreturnclose
  • 这些按钮用于控制桌面歌词的显示样式(如卡拉OK模式、双行/单行、锁定位置等)。

十、解析器的统一设计模式

面对如此庞大的XML,我们不应急于为每个窗口写一套解析代码。设计一个通用的节点映射框架

  1. 定义基类SkinComponent,包含x, y, width, height, image, type等通用属性。
  2. 根据节点名称(tag)创建具体子类ButtonComponentSliderComponentLabelComponentListComponentVisualComponent等。
  3. 解析时递归遍历:对每个窗口节点(如<player_window>),实例化对应的WindowConfig对象,存储其子控件列表。
  4. 在Swing启动时,遍历WindowConfig,为每个控件创建对应的JComponent,设置setBounds,并添加到JLayeredPane或带null布局的面板中。
  5. 事件绑定:在控件创建时,根据idtag添加对应的ActionListenerMouseAdapter,调用播放器的业务逻辑。

示例伪代码(解析按钮):

Element btnElem = (Element) node;
String[] pos = btnElem.getAttribute("position").split(",");
int x = Integer.parseInt(pos[0]);
int y = Integer.parseInt(pos[1]);
int right = Integer.parseInt(pos[2]);
int bottom = Integer.parseInt(pos[3]);
int width = right - x;
int height = bottom - y;
String imgPath = btnElem.getAttribute("image");
ImageIcon icon = new ImageIcon(loadImage(imgPath));
JButton btn = new JButton(icon);
btn.setBounds(x, y, width, height);
btn.setBorder(null);
btn.setContentAreaFilled(false);
// 添加事件
if (tag.equals("play")) {
    btn.addActionListener(e -> player.play());
}
// ...
container.add(btn);

写在最后:XML是骨骼,Swing是血肉

千千静听的皮肤体系之所以强大,是因为它将UI的所有几何信息、图片资源和交互状态彻底剥离出代码。而Java Swing恰好提供了足够的自由度去实现这种“像素级控制”——只要你有耐心解析XML、按坐标放置组件、并用paintComponent精细绘制每一个像素。

通过今天的拆解,你不仅看懂了这份官方皮肤文件,更掌握了将任何二维UI蓝图“翻译”成Swing程序的通用方法论。下一期,我们将进入播放列表引擎,实现千首歌曲的快速加载、排序和拖拽,敬请期待!


如果你觉得这篇文章帮你理清了Swing自绘的思路,别忘了点个「在看」或分享给同样热爱复古技术的朋友!评论区聊聊——你当年用过千千静听的“移动设备”功能吗?


## License

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

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

![image](https://img2024.cnblogs.com/blog/2716747/202608/2716747-20260827163950613-1424108556.png)
posted @ 2026-08-28 10:13  tomj2ee  阅读(4)  评论(0)    收藏  举报