原文地址:http://blog.csdn.net/mc_hust/article/details/51534901

自从准备毕业论文开始,就没写过博客了,关注量也明显呈下滑趋势(虽然本来就少)。到现在已经入职一个多月了,抽空把之前做的一个项目整理一下,算是毕业后的第一篇博客吧。


关于Mp3播放器,网上有各种实现方法,但是对于歌词的同步以及滑动更改播放进度的讲解却少之又少,所以我这里重点放在歌词的设计上(需要完整代码的朋友,可以在评论中留下邮箱,我会尽快回复),关于Mp3的“播放\切歌\暂停”以及“随机\顺序\单曲”播放等常用功能应该还是比较好做的。下面看看效果:

  • 主界面如下图:


    图1 - 主界面

  • 右滑之后进入歌词界面:


    图2 - 右滑进入歌词界面

  • 点击右上角那个大设置按钮:


    图3 - 设置界面


整个项目主要涉及到以下知识点:
- ViewPager
- Service与Activity通信
- Broadcast
- ContentResolver
- PreferenceActivity
- MediaPlayer
以上几个知识点大家应该比较熟悉,,四大组件全用上了,个人觉得这是个比较好的练手项目。下面从播放开始看吧。


1、MP3播放器Service

作为播放器,固然是需要能够支持后台播放的,所以在启动播放之前,需要开启service。为了方便Activity与Service通信,这里通过bindService方法开启Service,代码如下:

bindService(new Intent(MainActivity.this, PlayService.class), connection, Context.BIND_AUTO_CREATE);

其中connection是Servive的一个回调方法,在里面获取Mp3Binder:

private ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {PlayService.Mp3Binder binder = (PlayService.Mp3Binder) service;player = new Mp3Player(binder.getService(), musicInfos);}@Overridepublic void onServiceDisconnected(ComponentName name) {}
};

上面有个player,这个就是对播放器播放、暂停、切歌等操作的一个封装类,下面来看看:


2、Mp3的播放、暂停、切歌

为了方便使用,将Mp3的播放操作封装到Mp3Player类中,在里面我实现了Mp3的各种常用操作,以及循环、单曲、顺序播放等常用播放模式,通过此类与Service通信,即可完成对MediaPlayer的操作。


3、MediaPlayer的使用

MediaPlayer的使用应该还是很简单的,如果没有做过MediaPlayer开发的朋友,需要注意几个问题:
1. 在播放之前一定要先重置、准备。调用的顺序为:reset、setDataSource、prepare、start。
2. 由于播放的歌曲通常是在SD卡上,记得要申明权限:

3. 因为涉及到搜索歌词、以及随机播放的时候需要计算下一首歌,那么我们分别需要捕捉播放开始和播放结束的信号,可以使用两个监听器完成,如下:

mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {sendBroadcast(new Intent(MainActivity.Mp3Receiver.ACTION_NEW));}
});
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {sendBroadcast(new Intent(MainActivity.Mp3Receiver.ACTION_END));}
});

这里我通过广播的方式将“开始播放”和“结束播放”两个信号传递出去。


4、获取歌曲列表

说了这么多,下面开始搜歌吧。这里用到Android的ContentProvider,Android系统会搜索手机里所有的音频文件,并放在MediaStore下面,我们要做的就是从这里面拿出想要的数据。通过

context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);

可以拿到列表的cursor,然后在当中去逐条获取信息即可。把每一个音频文件视为一个对象,可以如下定义音频对象:

class MusicInfo {long id;String title;String artist;String duration;int durationInSeconds;long size;String data;long albumId;@Overridepublic boolean equals(Object o) {data = data.replace("file://", "");return data.equals(((MusicInfo) o).data);}
}

这样从Cursor中获取数据之后填写到上面MusicInfo中就可以了,代码示意如下:

private static List<MusicInfo> getMusicInfoList(Context context) {Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);List<MusicInfo> list = new ArrayList<>();int count = cursor.getCount();while (count-- > 0) {cursor.moveToNext();if (0 == cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC))) {continue;}MusicInfo info = new MusicInfo();info.id = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID));info.artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));long durationSeconds = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)) / 1000;info.durationInSeconds = (int) durationSeconds;info.duration = durationSeconds % 60 < 10 ? durationSeconds / 60 + ":0" + durationSeconds % 60 : durationSeconds / 60 + ":" + durationSeconds % 60;info.size = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));info.title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));info.data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));info.albumId = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));list.add(info);}return list;
}

这样拿到一个list然后设置到ListView中就可以完成歌曲列表的显示了。


5、搜索歌词

搜索歌词的原理其实就是在当前歌曲目录下去搜索同名的.lrc文件,然后从中读入数据流进行解析,歌词的解析可以参考lrc歌词的协议自行完成(需要完整代码可以在下面留下您的邮箱)。


6、歌词部分

接下来就是歌词的同步与歌词的滑动了,网上对于同步的实现大多是采用自定义一个TextView,然后再onDraw当中去用Paint画笔来画出歌词。这样做对于同步显示来讲非常容易,但是如果想让他在切换歌词的时候平滑移动以及拖拽歌词改变播放进度这都是比较麻烦的。因此这里我采用ListView来做歌词,这样平滑移动和滑动监听都比较方便。

由于需要将歌词放在屏幕中央,所以需要提前计算出屏幕中央是ListView的第几个Item,然后在前后依次留相应数据的空白。例如第五个item在中间,则在设置歌词数据的时候需要在前后分别留5个空白(示意代码,不建议这么写):

public void setLrcList(List<Lrc> lrcList) {//设置歌词内容this.lrcList = lrcList;//在歌词后留白lrcList.add(new Lrc());lrcList.add(new Lrc());lrcList.add(new Lrc());lrcList.add(new Lrc());lrcList.add(new Lrc());lrcList.add(new Lrc());//在歌词前留白lrcList.add(0, new Lrc());lrcList.add(0, new Lrc()); lrcList.add(0, new Lrc());lrcList.add(0, new Lrc());lrcList.add(0, new Lrc());lrcList.add(0, new Lrc());}
6.1 同步平滑更新歌词

通过update方法封装更新功能:

/*** 更新歌词内容** @param position 当前歌曲播放的时间*/
public void update(int position) {if (!isTouching) {adapter.notifyDataSetChanged();isAutoScroll = true;lvLrc.smoothScrollToPositionFromTop(adapter.update(position) - 4, 0, 1000);   //减4是保证当前这句歌词能显示在正中间}
}
  • 这里对ListView的滑动没有用到smoothScrollToPosition(int position);原因是这个函数仅仅是保证position的那个item会显示出来,而我们想要的效果是让他显示到正中间,所以只能用smoothScrollToPositionFromTop,让第前四句歌词显示在最顶端来实现效果。
  • adapter.update(position):这个方法的作用是获取歌曲播放到position时间的时候是第几句歌词,从而让他显示在中间,代码如下:
public int update(int position) {for (int i = 0; i < lrcList.size() - 1; i++) {//判断当前播放时间是否在歌词的第一句和最后一句歌词时间内if (position >= lrcList.get(i).getLrcTime() && position < lrcList.get(i + 1).getLrcTime() || position < lrcList.get(0).getLrcTime()) {index = i;break;}//如果时间超过了最后一句歌词,则停留在最后一句歌词else if (position > lrcList.get(lrcList.size() - 1).getLrcTime()) {index = lrcList.size() - 1;}}    return index;
}

这类似一个顺序查找算法,当然朋友们可以采用二分查找等其他算法提高效率。

这里实现的界面是一个ViewPager,第一页是歌曲列表,右滑到第二页是歌词。效果见上图

6.2 拖拽歌词改变播放进度

这部分主要是对歌词布局,即ListView的触摸监听操作,采用listView.setOnTouchListener来实现,先来看看这部分代码:

lvLrc.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:isTouching = true;break;case MotionEvent.ACTION_UP:int time = lrcList.get(lvLrc.getFirstVisiblePosition() + 5).getLrcTime();((MainActivity) activity).resume(time / 1000);isTouching = false;break;case MotionEvent.ACTION_CANCEL:isTouching = false;break;}return false;}});

主要是在ACTION_UP的时候进行操作,计算出当前播放的歌词的时间字段,然后通过service控制播放进度(resume中封装了对service的操作)。可以看到,在ACTION_DOWN和ACTION_CANCEL中也做了操作,主要是设置isTouching的值。这是为了防止在我们正在拖拽歌词的过程中,由于歌词同步作用导致当前歌词改变从而使歌词的ListView自动滑动。为了防止这个矛盾的出现,在歌词同步函数(update)中需要先检查isTouch的值,然后决定是否要进行自动同步(代码见6.1)。


7、设置界面PreferenceActivity

设置界面几乎是所有的App都要用到的,PreferenceActivity就是专门为设置界面打造的,而Android原生代码中几乎所有的设置界面也都是通过这个完成的。PreferenceActivity的使用方法网上有很多,他的使用与一般的布局类似,主要有以下几种类型:
* ListPreference 列表项菜单
* EditTextPreference 编辑框菜单
* SwitchPreference 开关菜单
本项目中就使用了以上几种菜单项,其余的也大同小异。我们可以对菜单项按功能进行分组,每一组是一个PreferenceCategory,而所有的PreferenceCategory都属于一个PreferenceScreen,这样的层级关系非常明确,具体的菜单布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"    android:title="设置"><PreferenceCategory android:title="播放模式"><ListPreference
            android:defaultValue="单曲循环"android:entries="@array/play_mode"android:entryValues="@array/play_mode_value"android:key="@string/key_play_mode"android:title="选择播放模式" /></PreferenceCategory><PreferenceCategory android:title="歌词设置"><ListPreference
            android:entries="@array/lrc_color"android:entryValues="@array/lrc_color_value"android:key="@string/key_lrc_color"android:title="歌词颜色" /><ListPreference
            android:entries="@array/lrc_size"android:entryValues="@array/lrc_size_value"android:key="@string/key_lrc_size"android:title="歌词大小" /></PreferenceCategory><PreferenceCategory android:title="定时关机"><EditTextPreference
            android:summary="将在设置的分钟数后关机"android:title="请输入关机时间" /></PreferenceCategory><PreferenceCategory android:title="摇一摇切歌"><SwitchPreference android:title="开启摇晃切歌" /></PreferenceCategory>

Activity的代码也非常简单:

package com.example.machao10.mp3;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.SwitchPreference;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;public class SettingsActivity extends PreferenceActivity {ListPreference listPlayMode, listLrcSize, listLrcColor, listRing, listNotification, listSms;EditTextPreference etAutoShutdown;SwitchPreference switchShake;private void initPreference() {listPlayMode = (ListPreference) findPreference(getString(R.string.key_play_mode));SettingsChangeListener listener = new SettingsChangeListener();listPlayMode.setOnPreferenceChangeListener(listener);}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);addPreferencesFromResource(R.xml.settings);initPreference();}class SettingsChangeListener implements Preference.OnPreferenceChangeListener {@Overridepublic boolean onPreferenceChange(Preference preference, Object newValue) {String key = preference.getKey();return true;}}
}

当然,以上只是对设值界面进行了显示,还需要完成相应的逻辑和用户设置的持久化,这个大家可以参考PreferenceActivity的具体用法,这里我就不展开讲了,需要完整开发源码的,可以在下面留下邮箱,我会及时给您回复的。


好了,mp3播放器就讲到这里,主要是从逻辑结构上做的梳理,然后针对部分细节进行展开,并没有将完整的代码做一个串接,主要还是考虑到关于Mp3的功能网上有很多资料,只是在歌词那一块应该还是很空白的。也希望我的这个歌词方案能够给大家带来一些方便,同时大家有什么好的建议欢迎讨论~

——超低空

MP3歌词的同步与拖拽设计相关推荐

  1. C# WinForm 工作流设计 工作流程图拖拽设计 +GDI 绘制工作流程图

    C# WinForm 工作流设计 工作流程图拖拽设计 +GDI 绘制工作流程图 大概功能说明一下: 1.支持拖动绘制工作节点 2.支持移动每个节点的移动 3.支持直线连接节点 4.支持节点移动连接线自 ...

  2. JeecgBoot低代码平台 3.5.2,仪表盘版本发布!重磅新功能—支持在线拖拽设计大屏和门户

    项目介绍 JeecgBoot是一款企业级的低代码平台!前后端分离架构 SpringBoot2.x,SpringCloud,Ant Design&Vue3,Mybatis-plus,Shiro, ...

  3. 5.DIY可视化-拖拽设计1天搞定主流小程序-公告管理

    1.DIY可视化-拖拽设计1天搞定主流小程序-公告管理 公告管理-本教程均在第一节中项目启动下操作 1.DIY可视化-拖拽设计1天搞定主流小程序-公告管理 2.创建数据表 执行: php think ...

  4. html css拖拽设计,css绘制三角形 和 HTML拖拽事件

    利用css制作三角形 利用设置边框的三个边的长度和border实现三角形设置,并隐藏其他边 例子:#yz3{ display: inline-block; 0; height: 0; border-t ...

  5. 拖拽平台-h5拖拽设计渲染原理

    参考平台: (开源版本的拖拽生成H5平台,类似易企秀.人人秀.夸克h5的可视化搭建系统) 具体技术介绍和内容: 基于Vue2.0开发,通过拖拽的形式,生成页面的工具,类似易企秀.百度 H5 等工具的可 ...

  6. 11.DIY可视化-拖拽设计1天搞定主流小程序-小程序首页公告详情页面

    小程序首页公告详情页面 本教程均在第一节中项目启动下操作 小程序首页公告详情页面 前言 一.添加界面,布局 1.设定组件样式: 数据绑定 二. 新增接口 三:绑定公告 四.查看效果 五.动态参数设置 ...

  7. 低代码可视化报表开源工具,只要在线拖拽就能做出复杂数据报表

    平时苦于做报表的小伙伴们,今天TJ君给你们带来一个开源低代码可视化报表项目,JimuReport,来解决你们的报表难题! JimuReport,作为一个报表项目,它拥有类似excel的操作风格,简简单 ...

  8. JimuReport 1.3.7 首个正式版本发布,免费的可视化拖拽报表

    项目介绍 积木报表,一款免费的可视化Web报表工具,像搭建积木一样在线拖拽设计!功能涵盖,数据报表.打印设计.图表报表.大屏设计等! 秉承"简单.易用.专业"的产品理念,极大的降低 ...

  9. We7 2.7版:全拖拽建站

    1.挑战极限--模板拖拽式在线设计 全部采用最新的模板在线拖拽设计系统完成. 一般的拖拽系统都是采用目前博客系统使用的模式,版式固定,部件也是固定的,在固定的版式里进行拖拽,取得一定的自定义效果.但是 ...

最新文章

  1. 别再抱怨了,国内这么多优秀的Android资源你都知道吗?
  2. shell echo 换行 不换行 打印换行
  3. QMainWindow多线程demo
  4. 复习webpack4之实现简易的webpack
  5. 十七、二叉树的建立与基本操作
  6. git丢弃本地修改的所有文件(新增、删除、修改)
  7. UE3 关卡优化指南
  8. ORA-12505,TNS:listener does not currently know of SID given in connect descriptor(不知道的SID)
  9. 【Java8】Stream 由函数生成流:创建无限流 - 实现斐波纳契数列
  10. Java在WEB项目中获取文件路径
  11. 俄罗斯互联网提供商巨头Rostelecom遭遇DDoS攻击企图
  12. Normalize.css的使用及下载
  13. 关于键盘(总论8042)
  14. 三行九个点,用4条线段连接(扩展,用3条,用1条)
  15. 全数集结,云上相会 | 大势智慧2022新品发布会改为线上举行
  16. 一个普通的小活动让超市回头客源源不断?方案简单到爆
  17. EOS智能合约开发系列(19): 合约应当开源
  18. SVN Tortoise小乌龟在repo-browser上右键删除了文件目录 回滚解决办法
  19. 2020个人网站搭建指南(华为云+wordpress)
  20. 新学期,新flag | 新学期的目标

热门文章

  1. Android发送接收短信的代码示例
  2. 我的android足迹
  3. 怎么在电脑的右键新建菜单添加.py或者其他格式的文件
  4. 逆序字符串 和 字符串的逆序输出 的区别~
  5. 苹果iOS App上架流程,非iOS开发人员上架教程
  6. Mac-连接Windows远程桌面
  7. 新手拍短视频技术总结:真实 随性 用心
  8. Linux学习笔记(3)- 网络编程以及范例程序
  9. NumberPicker
  10. 轻型载货汽车(离合器及传动轴设计)