17 Android Studio开发实战:音乐播放器——浪花音乐
手机上的多媒体内容讲究声情并茂、悦目且悦耳,这样才能让用户的感官得到最大享受。影视播放器由于存在视频自身的画面,反而限制了开发者的施展空间;而音乐播放器允许定制播放画面,开发者有足够空间施展拳脚。本节以“音乐播放器—— 浪花音乐”为实战项目,通过该项目的编码练习巩固和提高开发者的实战技能。
1 设计思路
大家常见的主流音乐播放器(如QQ音乐、酷狗音乐、酷我音乐、网易云音乐、虾米音乐、百度音乐等)不外乎有3项播放功能:
(1)展示音乐和歌曲列表。
(2)在歌曲详情页面滚动展示歌词,并高亮显示当前正在播放的歌词片段。
(3)通过音乐控制条显示播放进度,并提供开始与暂停、拖动播放的功能。
只看文字描述有点抽象,还是先给出播放器的效果图,方便查找对应的功能。如图所示为播放器的歌曲列表页面。
点击顶部的“打开音乐文件”会弹出文件对话框,用于选择音频文件;底部是播放器的控制条,中间为当前手机上的所有音乐文件列表。点击某个音乐项,进入该音乐的详情页面,如图所示。
页面顶部显示歌曲名称和演唱者,页面底部是播放器控制条,页面中间为该歌曲对应的歌词内容。
接下来对音乐播放器的3项功能进行详细剖析。
对于第一点的展示歌曲列表,让用户手动添加不但费时费力,而且用户往往搞不清楚手机上的歌曲都放在哪个目录。我们假设用户是“傻白甜”,开发者做的App就得智能贴心,主动帮用户把手机上的歌曲找出来。要想实现这个功能,可以通过内容组件访问系统自带的媒体库,查找并显示媒体库中的歌曲列表。
对于第二点的滚动歌词显示,常见的歌词文件是LRC格式的文本文件,内容主要是每句歌词的文字与开始时间。文本文件的解析并不复杂,难点主要是滚动显示。乍看歌词从下往上滚动,适合采用平移动画,然而歌词滚动不是匀速的,因为每句歌词的间隔时间并不固定,只能把整个歌词滚动分解为若干动画,有多少行就有多少个动画。
对于第三点的音乐控制条,总体上使用前面提到的视频控制条。不过音乐控制条更加复杂,因为除了控制音频的播放,还要控制歌词动画的播放。另外,音乐控制条显示在歌曲列表页面上,为了与主流播放器看齐,最好在系统通知栏固定放置音乐控制条。
弄懂了音乐播放器的主要功能,再来看该播放器用到的App开发技术。
(1)服务Service:歌曲播放不依赖于某个页面,即使用户回到桌面,歌曲也要继续播放,因此必须在后台服务中播放歌曲。
(2)应用Application:正在播放的歌曲名称,在播放器的任何页面都能看到,用到了全局内存,要把歌曲名称保存在自定义的Application类中。
(3)内容解析器ContentResolver:系统媒体库中的音频文件,需要通过内容解析器访问媒体库的音频资源,详细路径是MediaStore.Audio.Media.EXTERNAL_CONTENT_URI。
(4)文件存取:歌词文件与音乐文件在同一个目录下,文件名一样,只是扩展名变为lrc。
(5)通知Notification:系统通知栏要显示音乐控制条,就得把后台服务以通知的形式在前台运行。
(6)媒体播放器MediaPlayer:播放音频文件,自然会用到媒体播放器。
(7)音频控制条AudioController:跟影视播放器一样,自定义样式的控制条更能满足个性化的定制要求。
(8)按键事件KeyEvent与音量对话框VolumeDialog:用户按手机侧面的加、减键,播放器应弹出音量调节对话框,供用户调整音量大小。
(9)动画Animation:歌词的滚动显示,可使用平移动画,也可使用属性动画实现歌词滚动效果。
(10)其余高级控件:如列表视图ListView、进度条ProgressBar、拖动条SeekBar等,有待读者进一步发掘。
不看不知道,一看吓一跳。如果仅播放声音,技术上只要Activity加MediaPlayer就行,最多再加一个媒体控制条MediaController,三板斧够用了。但是要让播放器变得生动活泼,要让用户真正去欣赏音乐,开发者要做的工作就不是实现基础功能,而是从界面设计到用户体验,每个细节都要充分考虑,所以实际运用的技术远远不止三板斧。
2 小知识:可变字符串SpannableString
大家都知道,文本控件家族显示文本内容使用setText方法,使用setTextColor方法设置文本颜色,使用setTextSize方法设置文本大小,使用setTextAppearance方法设置文本样式(包括颜色、大小、风格等)。普通的用法只能对控件的所有文本做统一设置,如果想对前一段文本加大加粗,对中间一段文本显示红色,再将后面一段文本换成图像,就无能为力了。为了解决分段文本使用不同样式的需求,Android提供了可变字符串SpannableString,通过该工具实现对文本分段显示。
SpannableString的原理是给指定位置的文本赋予对应的样式,从而告知系统这段文本的显示方式。具体到编码有3个步骤,说明如下:
-
步骤1
从指定文本字符串构造一个SpannableString对象。
-
步骤2
调用SpannableString对象的setSpan方法设置指定文本段的显示风格。该方法的第一个参数为风格样式对象,第二个参数为文本段的起始位置,第3个参数为文本段的终止位置,第4个参数为风格的范围标志,用来标识在文本段前后输入新字符时是否令它们应用这个风格(主要对EditText起作用)。风格范围标志的取值说明见下表。
-
步骤3
用文本控件对象的setText方法设置定义好的SpannableString对象。
显示风格的定义在android.text.style包中,总共有30多个。当然,常用的没这么多,笔者整理了8个常用的显示风格,详见下表。
下面是使用SpannableString设置文字样式的代码:
SpannableString的不同风格效果如图1~图6所示。其中,如图1所示为加大字体后的效果,如图2所示为加粗字体后的效果,如图3所示为修改文字颜色后的效果,如图4所示为修改文字背景后的效果,如图5所示为增加下划线后的效果,如图6所示为把文字替换成图片后的效果。
读者是否对图6似曾相识?使用QQ聊天时会自动把特定字符转成表情图片,比如把文字内容中的“:)”显示为笑脸图片,在Android设备上可通过SpannableString实现该功能。
3 代码示例
编码与测试方面需要注意以下5点:
(1)如果把动画描述定义在XML文件中,动画定义文件就要放在res/anim目录下。
(2)打开音乐文件,要记得为AndroidManifest.xml添加SD卡的权限配置:
(3)AndroidManifest.xml的application节点注意补充android:name=".MainApplication";另外,注册音乐播放服务的service,注册代码如下:
(4)测试设备的Android版本要求不低于Android 4.4,因为属性动画的暂停和恢复方法是在4.4后引入的。
(5)要在真机上测试实战项目,如果在模拟器上测试,就会发现MP3标题乱码。这是因为中文歌曲的MP3标签采用GBK编码,而模拟器采用UTF8编码,两者对汉字的编码格式不一致。如果用真机测试,国产机厂商已经帮我们解决了汉字编码问题。
具体的代码编写还存在3个技术要点,记录如下:
1. 使用内容解析器ContentResolver访问媒体库
音频资源对应的内容路径是MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,内容解析器通过query方法访问该URI获得记录游标,还得把详细记录字段逐个读取出来,音频资源的字段信息说明见下表。
2. 解析LRC歌词文件
简要介绍一下LRC文件的内容格式,开发者关心的主要是内部的时间信息与歌词文字。下面是一个LRC歌词的片段:
歌词第一行有一个offset标签,表示歌词标注的时间与音乐文件的时间偏移。歌词行的前面是中括号括起来的时间戳,时间戳的数据格式为“分:秒.毫秒”,表示该行歌词的起始时间。如果某行歌词被演唱多遍,那么歌词文字前面会有多个时间戳。
3. 歌词滚动动画的播放控制
一般动画启动后很快就会结束,但歌词滚动动画不是这样的,用户点击控制条上的暂停按钮,不但播放器要暂停播放,而且歌词要暂停滚动。平移动画TranslateAnimation不支持暂停和恢复操作,不止平移动画,所有补间动画都不支持暂停和恢复。难道要自己重定义动画?山穷水尽疑无路,柳暗花明又一村。幸好Android提供了属性动画,不但支持所有补间动画效果,而且支持暂停和恢复操作,还等什么,赶紧把TranslateAnimation换成ObjectAnimator吧!
现在音乐播放器的编码没什么难点,如果不出状况,读者就能很快看到自己的App作品。下图所示为音乐播放器的效果画面。
点击歌曲列表中的歌名《一剪梅》,进入该歌曲的播放界面,歌词文字随着时间流逝缓慢向上滚动,当前演唱的歌词行会高亮显示。播放一段时间后,控制条的进度移到右边,歌词也大半上翻,高亮的歌词行移向后面的文字,如图所示。
接着按返回键,后退到歌曲列表页面,页面下方的控制条显示当前的播放进度,时间计数随着歌曲播放而不断刷新,如图所示。
在歌曲列表页面点击歌名《上海滩》,进入该歌曲的播放界面,此时《一剪梅》停止播放,转为播放《上海滩》,如图所示。
最后下拉系统通知栏,应该能够看到播放器的控制条,如图所示。在通知栏上不但可以自动刷新播放进度,而且可以进行暂停和恢复播放的操作。
下面是音乐播放详情界面与歌词有关的处理代码片段,更多源码参见源码media模块的MusicPlayerActivity.java、MusicDetailActivity.java和MusicService.java。