怀念经典,一步步开发一个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,即矩形区域。play和pause坐标相同,说明它们共用同一块热区,只是根据播放状态切换显示的图片(image)。- Swing实现:创建一个
ToggleButton或ImagePanel,监听点击事件,根据状态切换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_image和fill_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"/>
balance和surround是平衡和环绕调节滑块(范围较小)。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中,我们可以嵌入
JEditorPane或WebView(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节点是浏览器主体区域,我们将放置JEditorPane或JFXPanel。startup是一个复选框(ckbox_image),用于“启动时打开浏览器”。
八、移动设备面板(mobile_window)—— 同步歌曲到便携设备
<mobile_window position="897, 0, 1012, 602" image="mobile_skin.bmp">
- 这个窗口用于管理MP3等设备的文件传输,非常经典。
内部节点较多,列举关键:
dir、dirshow、dirspace显示设备目录和剩余空间。copylisttitle、copylisttitlebk标题和背景。listinfo文件列表区域,包含行高、颜色等属性。import、importok、importfinish、stopimport是复制进度相关的按钮。progress、progresstext、count显示复制进度和计数。picture节点引用了各种状态图标(删除、完成、错误等)。
Swing实现:这个窗口涉及文件IO和进度更新,适合用SwingWorker在后台执行复制任务,并更新进度条和文本。
九、桌面歌词条(desklrc_bar)—— 悬浮在屏幕上的歌词
<desklrc_bar image="desklrc_bar.bmp" transparent_color="#ff00ff">
- 这是一个横条工具栏,包含控制按钮和歌词显示?实际上
desklrc_bar本身可能只是一个控制条,真正的桌面歌词是独立的浮动窗口。但在这个XML里,desklrc_bar只定义了按钮,没有歌词显示区域。或许桌面歌词是通过另一个机制实现的(比如lyric_window的desklrc按钮切换)。
控件列表:
play/pause/prev/next等基本控制。list、settings、kalaok、lines、lock、ontop、return、close。- 这些按钮用于控制桌面歌词的显示样式(如卡拉OK模式、双行/单行、锁定位置等)。
十、解析器的统一设计模式
面对如此庞大的XML,我们不应急于为每个窗口写一套解析代码。设计一个通用的节点映射框架:
- 定义基类
SkinComponent,包含x, y, width, height, image, type等通用属性。 - 根据节点名称(tag)创建具体子类:
ButtonComponent、SliderComponent、LabelComponent、ListComponent、VisualComponent等。 - 解析时递归遍历:对每个窗口节点(如
<player_window>),实例化对应的WindowConfig对象,存储其子控件列表。 - 在Swing启动时,遍历
WindowConfig,为每个控件创建对应的JComponent,设置setBounds,并添加到JLayeredPane或带null布局的面板中。 - 事件绑定:在控件创建时,根据
id或tag添加对应的ActionListener或MouseAdapter,调用播放器的业务逻辑。
示例伪代码(解析按钮):
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


浙公网安备 33010602011771号