Android(安卓)实现简单音乐播放器(二)
在Android 实现简单音乐播放器(一)中,我介绍了MusicPlayer的页面设计。
现在,我简单总结一些功能实现过程中的要点和有趣的细节,结合MainActivity.java代码进行说明(写出来可能有点碎……一向不太会总结^·^)。
一、功能菜单
在MusicPlayer中,我添加了三个菜单:
search(搜索手机中的音乐文件,更新播放列表)、
clear(清除播放列表……这个功能是最初加进去的,后来改进之后,已经没什么实际意义)、
exit(退出)。
menu_main.xml
1 <menu xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:app="http://schemas.android.com/apk/res-auto" 3 xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> 4 <item android:id="@+id/action_search" android:title="search" 5 android:orderInCategory="100" app:showAsAction="never" /> 6 <item android:id="@+id/action_clear" android:title="clear" 7 android:orderInCategory="100" app:showAsAction="never" /> 8 <item android:id="@+id/action_exit" android:title="exit" 9 android:orderInCategory="100" app:showAsAction="never" />10 </menu>View Code
关于菜单功能,直接上代码,很简单,就不做说明啦。重要的在后面。
1 @Override 2 public boolean onCreateOptionsMenu(Menu menu) { 3 // Inflate the menu; this adds items to the action bar if it is present. 4 getMenuInflater().inflate(R.menu.menu_main, menu); 5 return true; 6 } 7 8 @Override 9 public boolean onOptionsItemSelected(MenuItem item) {10 // Handle action bar item clicks here. The action bar will11 // automatically handle clicks on the Home/Up button, so long12 // as you specify a parent activity in AndroidManifest.xml.13 int id = item.getItemId();14 15 //noinspection SimplifiableIfStatement16 if (id == R.id.action_search) {17 progressDialog=ProgressDialog.show(this,"","正在搜索音乐",true);18 searchMusicFile();19 return true;20 }else if(id==R.id.action_clear){21 list.clear();22 listAdapter.notifyDataSetChanged();23 return true;24 }else if(id==R.id.action_exit){25 flag=false;26 mediaPlayer.stop();27 mediaPlayer.release();28 this.finish();29 return true;30 }31 return super.onOptionsItemSelected(item);32 }View Code
二、搜索音乐文件——search的实现
先看一下相关的全局变量:
1 private ListView musicListView;2 private SimpleAdapter listAdapter;3 private List<HashMap<String,String>> list=new ArrayList<>();
为了播放音乐的便利,在播放器打开时,程序自动搜索音乐数据,将必要的信息保存在list中,并用ListView显示出来,以供用户进行选择。
而这个MusicPlayer用于播放手机外部存储设备(SD卡)的音乐,要搜索出SD卡中的全部音乐文件,主要有两种方法:1、直接遍历SD卡的File,判断文件名后缀,找到音乐文件。这种方法可以区别出一定格式的音乐文件,也可以找到对应的歌词文件,但是缺点是:遍历搜索,速度很慢。2、用Android提供的多媒体数据库MediaStore,直接用ContentResolver的query方法,就可以对MediaStore进行搜索啦,非常高效(果断选用这种方式~~),但是数据库里面没有歌词(泪目T_T~~~暂时放弃歌词播放的功能啦,以后要是想起来,再加上吧……)
1 private void searchMusicFile(){ 2 // 如果list不是空的,就先清空 3 if(!list.isEmpty()){ 4 list.clear(); 5 } 6 ContentResolver contentResolver=getContentResolver(); 7 //搜索SD卡里的music文件 8 Uri uri= MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 9 String[] projection={10 MediaStore.Audio.Media._ID, //根据_ID可以定位歌曲11 MediaStore.Audio.Media.TITLE, //这个是歌曲名12 MediaStore.Audio.Media.DISPLAY_NAME, //这个是文件名13 MediaStore.Audio.Media.ARTIST,14 MediaStore.Audio.Media.IS_MUSIC,15 MediaStore.Audio.Media.DATA16 };17 String where=MediaStore.Audio.Media.IS_MUSIC+">0";18 Cursor cursor=contentResolver.query(uri,projection,where,null, MediaStore.Audio.Media.DATA);19 while (cursor.moveToNext()){20 //将歌曲的信息保存到list中21 //其中,TITLE和ARTIST是用来显示到ListView中的22 // _ID和DATA都可以用来播放音乐,其实保存任一个就可以23 String songName=cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));24 String artistName=cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));25 String id=Integer.toString(cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media._ID)));26 String data=Integer.toString(cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)));27 HashMap<String,String> map=new HashMap<>();28 map.put("name",songName);29 map.put("artist",artistName);30 map.put("id",id);31 map.put("data",data);32 list.add(map);33 }34 cursor.close();35 //搜索完毕之后,发一个message给Handler,对ListView的显示内容进行更新36 handler.sendEmptyMessage(SEARCH_MUSIC_SUCCESS);37 }
搜索完了,要对ListView进行更新,这里的更新,在Handler中完成(也包括后面要讲到的播放时间的实时更新)。
1 private Handler handler=new Handler(){ 2 @Override 3 public void handleMessage(Message message){ 4 switch (message.what){ 5 //更新播放列表 6 case SEARCH_MUSIC_SUCCESS: 7 listAdapter=new SimpleAdapter(MainActivity.this,list,R.layout.musiclist, 8 new String[]{"name","artist"}, new int[]{R.id.songName,R.id.artistName}); 9 MainActivity.this.setListAdapter(listAdapter);10 Toast.makeText(MainActivity.this,"找到"+list.size()+"份音频文件",Toast.LENGTH_LONG).show();11 progressDialog.dismiss();12 break;13 //更新当前歌曲的播放时间14 case CURR_TIME_VALUE:15 currtimeView.setText(message.obj.toString());16 break;17 default:18 break;19 }20 }21 };
三、选择歌曲
好了,现在我们已经有了播放列表,那么下一个步骤自然是选择要播放的歌曲咯。
我们先来看一下播放器的不同状态:
1 // 定义当前播放器的状态2 private static final int IDLE=0; //空闲:没有播放音乐3 private static final int PAUSE=1; //暂停:播放音乐时暂停4 private static final int START=2; //正在播放音乐
选择歌曲,在IDLE状态下才有效。选中歌曲之后,要在具有跑马灯效果的TextView中显示歌名,并且更新播放总时长。
1 @Override 2 protected void onListItemClick(ListView l, View v, int position, long id) { 3 super.onListItemClick(l, v, position, id); 4 if(currState==IDLE) { 5 // 若在IDLE状态下,选中list中的item,则改变相应项目 6 HashMap<String, String> map = list.get(position); 7 nameChecked = map.get("name"); 8 Long idChecked = Long.parseLong(map.get("id")); 9 //uriChecked:选中的歌曲相对应的Uri10 uriChecked = Uri.parse(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + idChecked);11 nameView.setText(nameChecked);12 currPosition = position; //这个是歌曲在列表中的位置,“上一曲”“下一曲”功能将会用到13 }14 }
四、播放
有关播放的全局变量:
1 private MediaPlayer mediaPlayer;2 private TextView currtimeView;3 private TextView totaltimeView;4 private SeekBar seekBar;5 private AlwaysMarqueeTextView nameView;6 private ImageButton playBtn;
这里的播放,指的是音乐播放器的播放按钮,它要实现的功能有两个:1、IDLE状态下,按下即开始播放;2、播放时,按下,暂停;再按下,继续播放(这两个状态分别对应两种按钮图片)。
1 ExecutorService executorService= Executors.newSingleThreadExecutor(); 2 public void onPlayClick(View v){ 3 switch (currState){ 4 case IDLE: 5 start(); 6 currState=START; 7 break; 8 case PAUSE: 9 mediaPlayer.start();10 playBtn.setImageDrawable(getResources().getDrawable(R.drawable.player_pause));11 currState=START;12 break;13 case START:14 mediaPlayer.pause();15 playBtn.setImageDrawable(getResources().getDrawable(R.drawable.player_play));16 currState=PAUSE;17 break;18 }19 }20 private void start(){21 if(uriChecked!=null){22 mediaPlayer.reset();23 try {24 mediaPlayer.setDataSource(MainActivity.this,uriChecked);25 mediaPlayer.prepare();26 mediaPlayer.start();27 initSeekBar();28 nameView.setText(nameChecked);29 playBtn.setImageDrawable(getResources().getDrawable(R.drawable.player_pause));30 currState=START;31 executorService.execute(new Runnable() {32 @Override33 public void run() {34 flag=true;35 while(flag){36 if(mediaPlayer.getCurrentPosition()<seekBar.getMax()){37 seekBar.setProgress(mediaPlayer.getCurrentPosition());38 Message msg=handler.obtainMessage(CURR_TIME_VALUE,39 toTime(mediaPlayer.getCurrentPosition()));40 handler.sendMessage(msg);41 try {42 Thread.sleep(500);43 } catch (InterruptedException e) {44 e.printStackTrace();45 }46 }else {47 flag=false;48 }49 }50 }51 });52 } catch (IOException e) {53 e.printStackTrace();54 }55 }else{56 Toast.makeText(this, "播放列表为空或尚未选中曲目", Toast.LENGTH_LONG).show();57 }58 }
在播放时,播放进度体现在当前播放时长和进度条的变化上。因此,按下播放键时,我们要对进度条进行初始化。
1 private void initSeekBar(){2 int duration=mediaPlayer.getDuration();3 seekBar.setMax(duration);4 seekBar.setProgress(0);5 if(duration>0){6 totaltimeView.setText(toTime(duration));7 }8 }
播放过程中,实时更新播放时间和进度条的工作则用一个ExecutorService来完成。
把时长(毫秒数)转化为时间格式(00:00)的方法:
1 private String toTime(int duration){2 Date date=new Date();3 SimpleDateFormat sdf=new SimpleDateFormat("mm:ss", Locale.getDefault());4 sdf.setTimeZone(TimeZone.getTimeZone("GMT+0"));5 date.setTime(duration);6 return sdf.format(date);7 }View Code
五、停止
1 private void stop() {2 initState();3 mediaPlayer.stop();4 currState = IDLE;5 }
停止功能很简单,注意在停止播放时,更新必要的信息(包括按钮、状态、进度条、时间等等),我就不赘述啦
六、上一曲/下一曲
这两个功能恰好对立,实现起来原理都是一样的。这里我就只贴出上一曲的程序咯。
1 private void previous(){ 2 if(musicListView.getCount()>0){ 3 if(currPosition>0){ 4 switch (currState){ 5 case IDLE: 6 musicListView.smoothScrollToPosition(currPosition - 1); 7 musicListView.performItemClick( 8 musicListView.getAdapter().getView(currPosition-1,null,null), 9 currPosition-1,10 musicListView.getItemIdAtPosition(currPosition-1));11 break;12 case START:13 case PAUSE:14 stop();15 musicListView.smoothScrollToPosition(currPosition - 1);16 musicListView.performItemClick(17 musicListView.getAdapter().getView(currPosition - 1, null, null),18 currPosition - 1,19 musicListView.getItemIdAtPosition(currPosition-1));20 break;21 }22 }else{23 switch (currState) {24 case IDLE:25 musicListView.smoothScrollToPosition(musicListView.getCount() - 1);26 musicListView.performItemClick(27 musicListView.getAdapter().getView(musicListView.getCount()-1, null, null),28 musicListView.getCount()-1,29 musicListView.getItemIdAtPosition(musicListView.getCount()-1));30 break;31 case START:32 case PAUSE:33 stop();34 musicListView.smoothScrollToPosition(musicListView.getCount() - 1);35 musicListView.performItemClick(36 musicListView.getAdapter().getView(musicListView.getCount()-1, null, null),37 musicListView.getCount()-1,38 musicListView.getItemIdAtPosition(musicListView.getCount()-1));39 start();40 break;41 }42 }43 }44 }View Code
比较难的地方,就是如何在按下上一曲(或下一曲)的时候,实现出ListView的点击效果。
1 //使选中的歌曲滑动到页面显示范围内2 musicListView.smoothScrollToPosition(currPosition - 1);3 //单击ListView中的Item4 musicListView.performItemClick( musicListView.getAdapter().getView(currPosition-1,null,null),currPosition-1,5 musicListView.getItemIdAtPosition(currPosition-1));
七、退出时,释放MediaPlayer
1 @Override2 protected void onDestroy() {3 if(mediaPlayer!=null){4 mediaPlayer.stop();5 mediaPlayer.release();6 }7 super.onDestroy();8 }
八、用户权限
由于要播放SD卡中的音乐,我们还要在AndroidManifest.xml中添加读外部存储的权限。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
好了,到现在,一个拥有基本功能的音乐播放器就完工啦。
(总算写完了~~~)
更多相关文章
- Android热更新框架Nuwa的使用
- 下载最新Android代码的方法
- android-如何在子线程中更新ui
- 慎用原生MediaPlayer类播放音频
- Android热更新之AndFix就是个大坑
- 一些好的博客收集(持续更新中)
- Android集成Bugly热更新
- Android软件自动更新升级(自动下载安装新版本)
- Android实现简单的音乐播放