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 will

11         // automatically handle clicks on the Home/Up button, so long

12         // as you specify a parent activity in AndroidManifest.xml.

13         int id = item.getItemId();

14 

15         //noinspection SimplifiableIfStatement

16         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.DATA

16         };

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:选中的歌曲相对应的Uri

10             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                     @Override

33                     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中的Item

4  musicListView.performItemClick( musicListView.getAdapter().getView(currPosition-1,null,null),currPosition-1,

5                                musicListView.getItemIdAtPosition(currPosition-1));

七、退出时,释放MediaPlayer

1     @Override

2     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)