简介
之前做 TicktockMusic 音乐播放器,一个必要的需求肯定是歌词,在 github 上找了几个,发现或多或少都有点不满足需求,所以就自己动手写了一个。
先附上项目地址和效果图:
地址:https://github.com/Lauzy/LyricView
效果图:
需求
歌词的需求我想大家都很清楚,简单的话,直接打开一个音乐播放器查看一下。我们打开后分析一下歌词的功能:歌词完整的显示出来、当前歌词变色、可以根据时间而进行定位、可以手动滑动、滑动后显示一个指示器、点击指示器播放进度跳转、滑动时指示器变色等等。OK,我们自己写歌词控件,这些功能也是必不可少的,接下来就逐步分析下实现的过程。
实现
基本实现就是这三个过程,接下来一步步的分析。
歌词解析
首先,我们在网上下载一个歌词,即以 lrc 为后缀的文件。比如海阔天空这首歌的歌词,我们用记事本或者其他工具打开后就可以看到具体的歌词内容,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| [ti: 海阔天空] [ar:黄家驹] [al:乐与怒] [by:mp3.50004.com] [00:00.00]Beyond:海阔天空 [01:40.00][00:16.00]今天我寒夜里看雪飘过 [01:48.00][00:24.00]怀著冷却了的心窝飘远方 [01:53.00][00:29.00]风雨里追赶 ... [00:42.00]多少次迎著冷眼与嘲笑 [00:49.00]从没有放弃过心中的理想 [00:54.00]一刹那恍惚 ...
|
可以看到,歌词主要包含歌名、歌手、专辑、作者等头元素,以及歌词的主体内容,我们需要处理的就是主体的歌词内容。首先,歌词是一行一行的文本,其次,每行的文本都包含时间标签和具体的一行歌词,我们首先将歌词解析为一行行的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| InputStreamReader isr = null; BufferedReader br = null; try { isr = new InputStreamReader(inputStream, CHARSET); br = new BufferedReader(isr); String line; while ((line = br.readLine()) != null) { List<Lrc> lrcList = parseLrc(line); if (lrcList != null && lrcList.size() != 0) { lrcs.addAll(lrcList); } } sortLrcs(lrcs); return lrcs; }catch ...
|
解析为一行行的文字后,就需要具体的处理单行的文字了,我们可以看到,大部分歌词包含两种格式,即单个时间标签和多个时间标签,这里可以采用正则表达式来匹配文字,正则表达式为 (([\d{2}:\d{2}.\d{2}])+)(.*)
1 2 3 4
| [01:53.00][00:29.00]风雨里追赶 //多个时间标签 [00:42.00]多少次迎著冷眼与嘲笑 //单个时间标签
|
接下来根据正则表达式来解析单行歌词
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| private static List<Lrc> parseLrc(String lrcLine) { if (lrcLine.trim().isEmpty()) { return null; } List<Lrc> lrcs = new ArrayList<>(); Matcher matcher = Pattern.compile(LINE_REGEX).matcher(lrcLine); if (!matcher.matches()) { return null; } String time = matcher.group(1); String content = matcher.group(3); Matcher timeMatcher = Pattern.compile(TIME_REGEX).matcher(time); while (timeMatcher.find()) { String min = timeMatcher.group(1); String sec = timeMatcher.group(2); String mil = timeMatcher.group(3); Lrc lrc = new Lrc(); if (content != null && content.length() != 0) { lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000 + Long.parseLong(mil) * 10); lrc.setText(content); lrcs.add(lrc); } } return lrcs; }
|
这样,第一步就完成了,歌词解析完成后得到歌词的数据集合,每个元素都包括时间和内容。
歌词显示
歌词显示的思路就是将歌词一行行的画出来,我们首先假设屏幕足够大,那么只需要定位第一行歌词的位置,画出来第一行歌词,然后逐行下移一个固定的距离,再画出下一行歌词,依次类推,整个歌词内容就会全部花在画布上了。依照这个思路,我们可以先画出来文字。
1 2 3 4 5 6 7 8 9 10 11 12
| float y = getLrcHeight() / 2; float x = getLrcWidth() / 2 + getPaddingLeft(); for (int i = 0; i < getLrcCount(); i++) { if (i > 0) { y += textHeight + mLrcLineSpaceHeight; } ... canvas.drawText(text, x, y, mPaint); }
|
画出来文字的思路就是这样,首先从屏幕的中间开始,然后纵坐标每次增加文字的高度与距离之和,依次画出来每行文字。