一、前言:
第一次做安卓的个人项目,还是比较轻松的,将平时学的知识用到点子上就好容易,遇到自己想实现的而且还不会的就上网自己查,或者去官网的安卓开发者手册读老英文,这一块是最耗实践的,我在项目中在添加随机播放,加入歌曲时间,用什么数据结构存储歌曲信息等地方花了很大精力去学习,毕竟网上的东西查到10篇有3篇是符合的,就一篇详细而且有用的,所以遇到自己不会的时候效率还是蛮低的。
二、功能介绍:
音乐播放器可以将手机SD卡中的音乐读取后加入到音乐播放器中,列出列表共听者欣赏,在音乐播放界面,实现音乐播放功能,实现上下歌曲切换,调节音量大小,调节播放模式:如顺序播放,随机播放,单曲循环,也可以拖动进度条改变听歌快慢;也可以点击显示歌词查看歌曲歌词如果歌曲有歌词的话。
三、音乐播放器的设计:
1、知识:
在做音乐播放器之前需要掌握MediaPlayer、AudioManager、Simple Adapter等类的使用方法,用SeekBar实现应用场景,Hashmap存储歌曲信息,random类实现随机播放,监听事件以及异常处理。
2、界面的 设计:
当音乐播放器打开后,主界面以ListView的方式显示每首歌的歌曲名,歌曲图标,播放时间,存放位置。 我的项目中定义了俩个布局文件:
一个文件为主界面:main_layout.xml
一个是布局文件为ListView列表中的的每一行显示效果布局item_message.xml
在单击界面上的某一行歌曲信息时,打开音乐播放控制界面,在控制界面上可以实现上一首、下一首、播放、暂停、停止、音量增大或减小、设置播放模式:顺序播放、随机播放、单曲循环、还可以查看歌词等操作功能。该控制界面上可以显示正在播放的歌曲名称和歌曲播放进度,控制界面为music_play.xml
3、功能的设计:
(1)音乐列表界面的相关功能的设计:
该项目实现的 音乐播放器需要将歌曲存放在SD卡的playmusic目录下,所以当应用程序运行时,首先判断是否有SD卡,如果有SD卡的话,再判断SD卡上是否有playmusic目录,如果没有先创建;如果存在还要判断是否有有音乐文件并给与用户相应的提示,那么就使用ListView和SimpleAdapter将歌曲的图片、歌名、存放位置和歌曲事件等显示到界面上。
(2)播放控制界面的相关功能设计:
在单击音乐列表转到播放控制界面时,首要获取前一个界面由intent传递来的index和存放歌曲信息的数组,并播放index相应的歌曲,再自定义一个playmusic(int index)方法,通过不断调用playmusic方法实现音乐播放。
四、具体实现:
(1)判断SD卡上的playmusic目录和音乐文件的实现:
//1.判断SD卡playmusic有无
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
media_path = Environment.getExternalStorageDirectory().toString();
}
else {
Toast.makeText(MainActivity.this, "sorry,SDcard is not existed!", Toast.LENGTH_SHORT).show();
return;
}
File folder = new File(media_path+"/playmusic/");
//如果没有playmusic文件夹则需要创建
if(!folder.exists())
{
folder.mkdirs();
Toast.makeText(MainActivity.this,"sorry,no such music file!", Toast.LENGTH_SHORT).show();
finish();
return ;
}
(2)给ListView装配数据:
首先要用正则表达式筛选音乐文件:( "^.*?\\.(mp3|mid|wma)$" )
private static final FileFilter filter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return !pathname.isDirectory()&&pathname.getName().matches(("^.*?\\.(mid|mp3|wma)$")); //正则表达式判断MP3文件
}
};
在创建Hsahmap对象存储歌曲信息:
//2.定义音乐文件名:
File[] filearr = folder.listFiles(filter);
//把filearr给到listview
//把文件信息装到filearr中
HashMap<String,Object> mmap;
for(File file : filearr)
{
mmap = new HashMap<String,Object>();
mmap.put("icon",R.mipmap.musicon); //歌曲图标
mmap.put("filename",file.getName()); //歌曲名
mmap.put("filepath",file.getAbsolutePath()); //歌曲位置
MediaPlayer mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(file.getAbsolutePath());
long mTime = mediaPlayer.getDuration();
String sTime = String.format("%d:%d",
TimeUnit.MILLISECONDS.toMinutes((long) mTime),
TimeUnit.MILLISECONDS.toSeconds((long) mTime) - TimeUnit.MILLISECONDS.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) mTime)));
mmap.put("fileTime",sTime); //歌曲时间:260s->4min:260s-(4*60) = 20s == 4m20s
}catch (IOException e){
e.printStackTrace();
}
listItem.add(mmap);
}
*解释1:为什么去使用HashMap存储
HashMap 是一个散列表,它存储的是一组键值对(key-value)的集合,对于ArrayList 和 LinkedList,还有 Vector能更快速的查找和插入。
*解释2:如何得到的歌曲时间:
定义的对象mtime由duration()获取到的是毫秒,我用java中的timeunit中的toMinute()方法将毫秒转化成分钟,在用toSecond()方法将毫秒转化成秒;再用总秒数减去总分钟数就可得剩余秒数;在程序中有样例解释。
大概做出来的界面如下图:
(3)绑定ListView的单击事件:
单击ListView中的某一行时,用户跳转到播放控制界面,并将当前歌曲的index和playmusic下的所有歌曲信息用intent传递给PlayActivity。
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Intent intent = new Intent(MainActivity.this,PlayActivity.class);
intent.putExtra("index",position);
intent.putExtra("list",listItem);
startActivity(intent);
}
});
(4)自定义播放音乐playmusic方法:
最关键的一步创建MediaPlayer类,实现类中的方法。
private void playmusic(int index)//播放方法
{
tvname.setText(muslist.get(index).get("filename").toString()); //显示歌名
String path = muslist.get(index).get("filepath").toString(); //歌曲地址
if(TextUtils.isEmpty(path)) //判断文件是否为空
{
return ;
}
try{
if(mediaPlayer.isPlaying()) //正在播放先停止
{
mediaPlayer.stop();
}
mediaPlayer.release(); //释放资源
mediaPlayer = null; //防止野指针
mediaPlayer = new MediaPlayer();
mediaPlayer.reset(); //重置
mediaPlayer.setDataSource(path); //设置播放元
mediaPlayer.prepare(); //就绪
//mediaPlayer.start();
mediaPlayer.setOnPreparedListener(new SetPrepareListener());//设置歌曲准备完毕后的监听事件
mediaPlayer.setOnCompletionListener(new SetCompetionListener());//设置一首歌听完毕后的监听事件:时单曲,顺序还是随机
mediaPlayer.setOnErrorListener(new SetErrorListener()); //监听错误
}catch (IOException e){
e.printStackTrace();}
}
*解释3:我为什么要加监听:
因为要设置音乐播放设置,如顺序播放,所以我要在一首歌结束时设置监听事件以保证播放设置的正确性。
(5)播放设置的实现:
class SetCompetionListener implements MediaPlayer.OnCompletionListener
{
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
switch (playmode)
{
case 0: //顺序
index ++;
if(index >= muslist.size())
{
Toast.makeText(PlayActivity.this,"this is the last one",Toast.LENGTH_SHORT).show();
return ;
}
break;
case 1: //随机 添加random类
if(muslist != null)
{
if(random == null)
{
random = new Random();
}
index = random.nextInt(muslist.size());
//index为随机大小(不超过列表长度)
playmusic(index);
}
break;
case 2: //单曲
mediaPlayer.seekTo(0);
mediaPlayer.start();
break;
}
}
}
(6)实现上一首,下一首块进:
//2.播放设置:在下面定义方法
playmusic(index);
btn_up.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
index--; //不可小于0
if(index<=0)
{
Toast.makeText(PlayActivity.this,"This is the first music",Toast.LENGTH_SHORT).show();
return ;
}
playmusic(index);
}
});
btn_down.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
index++; //不可超过最大歌曲数
if(index>=muslist.size())
{
Toast.makeText(PlayActivity.this,"This is the last music",Toast.LENGTH_SHORT).show();
return ;
}
playmusic(index);
}
});
(7)进度条的实现:
//5.seekbar
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) { //当进度改变时执行的操作
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) { //当拖动滑块开始时执行的操作
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) { //当停止滑块时开始执行的操作
mediaPlayer.seekTo(seekBar.getProgress());
}
});
音乐播放器界面如下图:
五、改进:
现在的音乐播放器在来电话的时候就会出现问题,因为把控制音乐播放放在了activity里,所以我考虑将这个放在了service中,在服务中控制播放音乐,通过BroadcastReceiver传递一些数据,并且实现了在电话打过来时,停止播放音乐,打完电话继续播放。还有一个问题,就是查看歌词的设计能用轮播将歌词的速度与歌曲的速度同步就好了。