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

声明:本博客转载自郭霖微信公众号,优秀博客值得多次转播。
地址http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236950&idx=1&sn=d51e6420df7f7533bc81cfe98541c3c0&scene=1&srcid=0906oyCeaFXlCFdpHQGfTNKZ#rd
原文

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

码农小阿飞 的博客地址:
http://blog.csdn.net/mario_0824

前言

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

这里写图片描述
用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;//开始时间
}

解析歌词文件源码:

这里写图片描述

验证解析效果

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

这里写图片描述

效果图:

这里写图片描述

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

上面有提及到在早些前我有用 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了:
这里写图片描述

计算行高度

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

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

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

这里写图片描述
匹配当前播放行数 mCurrentPlayLine

这里写图片描述
计算偏移量scrollY

理论基础已经实现,初步尝试绘图 onDraw:
http://mmbiz.qpic.cn/mmbiz_png/v1LbPPWiaSt5PSLytzUYDFkj3x0ZdhXDiaeS5zUBdLH2m6quIR06CkBwzLuxQzNGapqMQToXia5FfqY9pU4Biaf6lg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

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

http://mmbiz.qpic.cn/mmbiz_png/v1LbPPWiaSt5PSLytzUYDFkj3x0ZdhXDiaEn4icGZ5gE5xneQBeqBHcDkPCmlf8FlCG98giaN4ILUe4jcPErySymrA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

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

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

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

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

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

这里写图片描述
ACTION_MOVE

http://mmbiz.qpic.cn/mmbiz_png/v1LbPPWiaSt5PSLytzUYDFkj3x0ZdhXDiaR5tTzyaGOpwlWTkWAC8XYvYVSeXoaw73e6WwXeSbnKGJDxzGBeRxng/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

阻尼大小计算

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

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

这里写图片描述

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

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

这里写图片描述
绘制指示器左侧播放按钮
这里写图片描述
绘制指示器分割线和时间

既然设计播放按钮,当然播放按钮就要实现点击事件啊:
这里写图片描述

播放按钮点击位置判断

这里写图片描述

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

效果图

这里写图片描述
overScroll效果展示

这里写图片描述
字体颜色设置效果展示

这里写图片描述
字体大小设置效果展示
http://mmbiz.qpic.cn/mmbiz_gif/v1LbPPWiaSt5PSLytzUYDFkj3x0ZdhXDiaUcSJVpiaBo3LicsUBOehK5e0ibiaFE2SPXIiawib5dmIrHNuoPziaD6yef07A/0?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1

行间距设置效果展示

这里写图片描述
指示器和播放按钮效果展示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值