自定义LyricView实现歌词显示控件

自定义LyricView实现歌词显示控件


码农小阿飞 之前发表的《做一个炫酷的悬浮迷你音乐盒反响很不错。正所谓好事多磨,时隔2个月之后,他又为大家带来了碉堡的歌词显示控件LyricView,我也不多说,大家可以直接看看文末的效果图集就知道了。


码农小阿飞 的博客地址:

http://blog.****.net/mario_0824


前言


这次我要向大家分享的是一个歌词控件,其实,也是我毕业设计中的一部分。起初我是用 ScrollView 嵌套 TextView,再结合我的上一篇文章SpannableString来实现的,Demo其实我早早地就将放在github上,不知道有没有朋友有留心看到,但是总感觉用着不是很流畅,而且不容易加入一些自定义内容,所以一直不好下笔,也不好向大家分享Demo。不过,有兴趣的朋友可以看一下,个人感觉还是挺有创意的,嘻嘻!(害羞脸~)。下载地址:

https://github.com/WuLiFei/LyricManager


自定义LyricView实现歌词显示控件
用ScrollView嵌套TextView实现LyricView效果图


效果图是旧版本哦,记住,是旧版本! 通过 自定义View 实现的"进阶版" LyricView 功能更强大,体验效果更佳,能够实现歌词滑动查看,当前播放位置高亮显示,滑动到指定位置并播放等等,总的来说,大致和网易云音乐的歌词显示效果一样。


歌词文件的组成


写过音乐播放器的朋友也应该都会去了解过歌词文件的规范格式,既然是歌词显示控件,就必然需要好好了解歌词文件的组成规范,才能准确无误的解析歌词文件,获得与我有用的信息。


[ti:一个人的北京]

[ar:好妹妹乐队]

[al:南北]

[by:]

[offset:0]

[00:00.10]一个人的北京 - 好妹妹乐队

[00:00.20]词:秦昊

[00:00.30]曲:秦昊

[00:00.40]

[00:30.16]你有多久没有看到 满天的繁星

[00:37.34]城市夜晚虚伪的光明 遮住你的眼睛

......

[04:48.87]离开了这里 在晴朗的天气

[04:55.08]

[04:56.27]让我拥抱你 在晴朗的天气


这如上述文本显示,是我在QQ音乐中下载的歌词文件,并用文本方式打开看到的一个结果。其实,所有 歌词文件(*.lrc)都是以一个标准来进行制作的,就如同上述文件一样由 ”[ti:”开头的标题、以”[ar:”开头的歌手、以”[al:”开头的专辑、以”[by:”开头的制作、以”[offset:”开头的时间偏移量和以”[mm:ss.ms]”开头的歌词信息 组成,歌词信息则是由 开始时间(分:秒.毫秒) 歌词内容 两部分组成。


解析歌词文件


既然了解了歌词文件的组成部分,那么解析歌词文件也就不是一件困难的事情了,就是简单的文件内容读取:首先获取*.lrc歌词文件的二进制流 InputStream,再又转换成字符流(注意:转化成字符流的时候需要选择编码,经过我多次尝试,发现好像QQ音乐的歌词文件需要用”GBK”解码,也不清楚会不会有具体的判别方式,有了解的朋友希望可以在留言区和大伙儿分享)。


然后再调用 BufferedReader readLine()方法 逐行读取文件内容,就能获得文件内容了,在这里有一点需要注意的是,各种流在使用结束后一定要调用close()方法 关闭。下面就是实现歌词文件的解析工作。


首先,需要准备两个类主要用于歌词解析结果的缓存:LyricInfo(歌词信息:包含标题、歌手、专辑等等)和 LineInfo(歌词行信息:包含行开始时间和歌词行内容):


LyricInfo 歌词文件信息:


class LyricInfo {
   List<LineInfo> song_lines;      String song_artist;//歌手    String song_title;//标题    String song_album;//专辑    long song_offset;//偏移量
}


LineInfo 歌词行信息:


class LineInfo {
   String content;//歌词内容    long start;//开始时间
}


解析歌词文件源码:


自定义LyricView实现歌词显示控件


验证解析效果


完成歌词解析,接下来就是验证歌词解析的一个实际效果的时候了:


自定义LyricView实现歌词显示控件


效果图:


自定义LyricView实现歌词显示控件


就这样,一个简单的歌词显示功能也就实现了。 但是,如何才能够让自己写的音乐播放器在歌词显示模块能够显得高大上,并且能够像很多当前应用市场上流行的音乐播放器那样,实现当前播放高亮显示、歌词回弹效果、歌词淡入淡出效果以及滑动歌词快速播放等功能呢? 请接着往下读…..


上面有提及到在早些前我有用 ScrollView 嵌套 TextView 的方式实现过自定义 LyricView,但是,由于体验效果和功能拓展上的不足,我并没有公开分享。既然通过 ScrollView 嵌套 TextView 的方式 不能满足 我们的设计需求,那是不是能够通过 自定义View 的方式实现 LyricView?既有如 TextView 那样的 LineHeigh(行高)、LineCount(总行数) 的概念,也有如 ScrollView 那样的 ScrollY(Y方向的偏移量) 的概念。那是必须的,说干就干。


LyricView实现


解析*.lrc歌词文件,生成歌词集合列表,获得行总数


上面我已经讲过解析歌词了,而在 LyricView 中,我们需要做的是将逐行解析出来的歌词信息添加到 集合mLyricInfo 中,而 总行数mLineCount 也就等于 List集合 的大小 mLineCount = mLyricInfo.song_lines.size()


计算歌词单行高度,获得歌词绘制区域总高度


写过 自定义View 的朋友应该都会知道,在 自定义View 中如果涉及文字的绘制,为了能够精准的绘制文字的位置,我们需要获取需要绘制的文字的矩形区域,通过画笔 Paint getTextBounds(String text, int start, int end, Rect bounds)方法 则可以帮助我们轻松获得一个需要绘制文字的一个矩形。


当然,绘制文字矩形区域的大小还与文字的大小相关,我们还可以通过画笔 Paint setTextSize(float textSize)方法 设置绘制文字大小,也就是常说的 TextSize


LyricView 中,行高可不仅仅就只是文字矩形区域的高度,行高还包括两行之间的行间距,如下图所示。既然歌词总行数和歌词单行高度我们都已取得,那么获取歌词绘制区域的总高度也就so easy了:


自定义LyricView实现歌词显示控件
计算行高度


定义 scrollY,并通过当前歌曲播放进度的时间戳计算 scrollY


既然 LyricView 能够实现滑动功能,那么引入 scrollY 记录滑动偏移量,并控制视图绘制效果也就顺理成章。 需要明确一点,当偏移量 scrollY 的值为零的时候,歌词的首行将显示在整个 LyricView 的正中间 。


我们知道每一句歌词中都包含着开始时间,而我们也就可以通过当前歌曲播放进度匹配当前播放的行数 mCurrentPlayLine,并通过当前播放所在行,计算偏移量 scrollY 的值,控制歌词播放滚动和当前播放位置的高亮显示。


自定义LyricView实现歌词显示控件
匹配当前播放行数 mCurrentPlayLine


自定义LyricView实现歌词显示控件
计算偏移量scrollY


理论基础已经实现,初步尝试绘图 onDraw:


自定义LyricView实现歌词显示控件


Bingo ! 歌词确实能够在屏幕上绘制出来,细心的朋友也许会发现其中的几个特别的地方,分别是当前播放位置高亮显示和歌词淡入淡出效果实现的代码:


自定义LyricView实现歌词显示控件


但是,仅仅只是实现显示功能,并且超出范围的歌词内容还不能通过滑动来查看。哈哈~ 别着急啊,骚年,坐下来和我凉茶,听我细细道来。


重写 onTouchEvent,实现歌词滑动查看,并实现 overScroll 回弹效果


仅仅需要实现滑动视图的功能的话,说实话,非常简单,只需要记录 ACTION_DOWN 时的 y值,并比较 ACTION_MOVE 过程中的 y值 计算两者的差值,生成新的偏移量 scrollY,再刷新视图,就可以搞定 !


要是就这么简简单单了事的话,怎么也不符合我个人对完美设计的要求。要是我们无限滑动的话,整个歌词内容区域就会滑动出我们的可视区域,也就是常说的 overScroll,如果不加以限制将会是一种非常差的用户体验。


当然,不同的开发对解决这个问题有不同的方法,有些播放器的歌词显示控件,当滑动事件出现 overScroll 时,将不再视图继续滑动。当然,也有当滑动事件出现 overScroll 时,视图依旧能够继续滑动,但与正常滑动时有所区别,这个时候的滑动会有一种 阻尼效果,也就是实际滑动距离和视图的滚动距离并不相等,而且随着 overScroll 的值越大,阻力越大,滑动越艰难,并在用户手指离开屏幕后回到 overScrol l的值为零的位置。当然,我本人更喜欢后者的用户体验,为了实现这个功能,那么就必须要在重写 onTouchEvent 的方法中”做点手脚”了。


自定义LyricView实现歌词显示控件
ACTION_MOVE


自定义LyricView实现歌词显示控件
阻尼大小计算


通过我一次一次对代码的细化,只要这么简单的两个方法,就完成了滑动时偏移量 scrollY 的计算,包括 overScroll 非overScroll,是的,只要这么两个方法。

到了这一步,歌词的显示、滑动查看都已经完成。但这还没完,我是不是还说过我的 LyricView 能够实现像网易云音乐歌词显示控件那样的指示器效果,哈哈哈 ~ 对于我这个完美主义者而言,这个功能必须实现。


自定义LyricView实现歌词显示控件
歌词指示器效果图


实现歌词指示器效果,”屌丝”蜕变”高富帅”


其实,指示器效果实现起来也不是很难,其实指示器左侧的按钮完全可以用绘制 Bitmap 的方式其实现,但是,考虑到 LyricView 的灵活性,同时,我们程序猿不都是能够用代码绘制的决不在工程中添加图片的嘛 !更何况就一个简单的播放按钮,随便画画,哈哈 ~ 至于,右侧的时间指示,则是通过当前偏移量 scrollY 的值计算得来的当前控件正中间位置显示歌词的开始时间。


自定义LyricView实现歌词显示控件
绘制指示器左侧播放按钮


自定义LyricView实现歌词显示控件
绘制指示器分割线和时间


既然设计播放按钮,当然播放按钮就要实现点击事件啊:


自定义LyricView实现歌词显示控件
播放按钮点击位置判断


自定义LyricView实现歌词显示控件


到这一步,我们的自定义 LyricView 设计介绍也就告一段落咯 ! 当然,功能远不止这些,还有 设置字体大小、设置行间距 以及 结合速度追踪器实现滑行效果 等等。所谓”授人以鱼不如授人以渔”,我想要和大家分享的是我的一个设计思路,大家可以根据需求设计不通的功能,因此在这里我也不做过多介绍,对小阿飞的 LyricView 感兴趣的朋友可以去我的gitHub下载研究:

https://github.com/WuLiFei/LyricViewDemo


效果图


自定义LyricView实现歌词显示控件
overScroll效果展示


自定义LyricView实现歌词显示控件
字体颜色设置效果展示


自定义LyricView实现歌词显示控件
字体大小设置效果展示


自定义LyricView实现歌词显示控件
行间距设置效果展示


自定义LyricView实现歌词显示控件
指示器和播放按钮效果展示


点击下方 阅读原文 可以直接跳转到源码的下载地址。



自定义LyricView实现歌词显示控件


如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。


欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

自定义LyricView实现歌词显示控件