思维导图如下:
背景知识自定义线程权限申请基本的重写View能力MediaPlayer工具来播放音乐SeekBar滑动条监听事件以及改变歌曲进度ListView绑定适配器展示数据以及Item点击事件Dialog弹出框启动页的设置界面设计
遵循界面简洁直观,操作方便快捷原则进行设计,点击右上角设置按钮会弹出选择框,用于设置播放模式。
整体布局使用LinearLayout布局,播放列表使用ListView控件,底部play_bar的布局使用RelativeLayout和LinearLayout嵌套使用,整体布局如左图,具体实现效果如右图:

ListView中Item布局如图:
设计想法
Activity文件
Center.java:主页面,主要包含了音乐播放器的播放暂停、继续播放、切歌等方法、SeekBar的监听事件和一些按键的点击事件Class文件MusicUtils.java:实现本地sdCard中音频文件的扫描以及时间格式的转化SongAdapter.java:自定义的适配器,继承自ArrayAdapter,泛型指定为Song类,完成ListView控件的实现ToastUtil.java:封装了Toast方法LunchActivity.java:实现启动页Song.java:歌曲的实体类,是自定义的一个类,类图如下:Layout文件
activity_center.xml : 主界面的layout设计song_list.xml : 此布局包括了一个Imageview用来显示歌曲图片,三个TextView用来显示歌曲的信息play_bar.xml:底部播放栏的layout设计程序时序图:播放音乐时序图:暂停播放时序图:切歌时序图(正在播放):若无歌曲播放,则直接调用mediaPlayer.setDataSource()来设置播放路径;
实现步骤编写MusicUtils扫描本地音频文件,返回一个List<Song>集合Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, selection,selectionArgs, MediaStore.Audio.AudioColumns.IS_MUSIC );申请权限,然后在手机内存sdCard将音乐及其相关信息读取出来,在清单文件中输入下列语句来申请权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />在清单文件中设置主题,并在style文件中设置相关参数来实现启动页:<style name="AppTheme"parent="Theme.AppCompat.Light.DarkActionBar"><item name="colorPrimary">@color/colorPrimaryDark</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">@color/colorAccent</item><item name="android:windowBackground">@drawable/path</item><item name="windowNoTitle">true</item></style>用一个ListView容器显示所有的本地音乐,在Center.java文件中定义List<Song>集合来保存获取到音频文件的相关信息,再绑定适配器来连接视图与数据list = MusicUtils.getMusicData(this);SongAdapter adapter = new SongAdapter(this, list);listView.setAdapter(adapter);编写相关函数,实现播放、暂停、下一首、上一首、继续播放等功能主要调用方法为:mediaPlayer.start()、mediaPlayer.pause()、mediaPlayer.stop()、mediaPlayer.seekTo();编写监听事件来控制SeekBar的更新以及改变进度,并通过自定义线程来实现UI界面的更新设置ListView中Item点击事件详细设计(含主要的数据结构、程序流程图、关键代码等)主要函数播放:musicplay(int position)暂停:pause(int position)继续播放:playAgain()下一首: next()上一首:front()图标、歌曲名更新: update(String song , boolean is_play)设置播放模式:showDialog(View view)关键代码启动页的实现:new Handler().postDelayed(new Runnable() {@Overridepublic void run() {Intent intent = new Intent(LunchActivity.this, Center.class);startActivity(intent);finish(); }}, 10000);ListView点击事件:设置参数isPlayAgain = 0来标记重复点击次数,第一次点击歌曲直接播放,第二次点击时判断是否为同一个,如果不是则切歌,是则暂停;第三次点击同样需要点击是否仍为同一个,如果不是则继续切歌,如果是则继续播放;if ( isplaying ){pause(playposition);//暂停播放if ( playposition != position ){//点击不是同一首歌曲,则更换播放路径,切歌,同时isPlayAgain=0}else {isPlayAgain++;//当isPlayAgain为偶数时,继续播放;为奇数则暂停播放 }}else { isplaying = true; //说明开始播放歌曲musicplay(position);//没有播放音乐,则开始播放}流程图如下:SeekBar进度条改变:当进度条改变时,通过seekBar的getProgress函数获取当前值,利用mediaPlayer的seekTo函数实现跳转int time = seekBar.getProgress();int all_time = list.get(playposition).getDuration();//获取正在播放歌曲的总长度int max = seekBar.getMax();mediaPlayer.seekTo(time all_time / max );thread = new Thread(new SeekBarThread());thread.start(); //创建进程自定义线程SeekBarThreadwhile ( isplaying && mediaPlayer.isPlaying() ) {seekBar.setProgress( time max / all_time );// 将SeekBar位置设置到当前播放位置try {// 每500毫秒更新一次位置Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}下一首,特殊情况下播放的歌曲为最后一首或还没有播放任何一首歌曲时,切歌播放的是列表第一首歌(播放第一首歌时进行上一首切歌的逻辑类似):if ( playposition == (list.size()-1 )|| !isplaying ){if ( flag ){mediaPlayer.stop();mediaPlayer.release();}musicplay(0);flag = true; //用于记录播放最后一首歌或第一首歌isplaying = true; }else { musicplay(playposition + 1 );}设置弹出框:AlertDialog.Builder builder1=new AlertDialog.Builder(Center.this);builder1.setTitle("请选择播放模式");builder1.setItems(gender, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {
//对how进行赋值ToastUtil.showMsg(Center.this,gender[which]+ how);} }); builder1.show();
设置播放模式:定义参数int how = 1(默认为顺序播放)来记录当前设置播放模式,并通过点击弹出框来获取how值:how = 0为单曲循环,how = 1为顺序播放,how = 2为随机播放。通过在musicplay()函数中调用mediaPlayer.setOnCompletionListener()来设置当前音频播放完毕后的操作:调用next()函数,SeekBar置为0。设置模式为随机播放时,根据以下规律来确定下一首歌的标号:if ( playposition < list.size() / 2 ){musicplay( playposition 2 - 1);}else musicplay( playposition / 2 + 1 );
播放音乐:mediaPlayer.reset(); //重置多媒体mediaPlayer.setDataSource(list.get(position).getPath());mediaPlayer.prepareAsync();//通过异步的方式装载媒体资源mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {mediaPlayer.start();}});//当前多媒体对象播放完成时发生的事件mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {public void onCompletion(MediaPlayer arg0) {
//如果当前歌曲播放完毕,自动播放下一首.}
});thread = new Thread(new SeekBarThread());thread.start();
五、实验结果与分析
实验结果:可以实现设计的需求,进行本地音频的扫描以及播放暂停、切歌等功能,使用起来体验感良好,满足了用户的基本需求,界面如图:
试验时应用在后台运行了半小时左右,总体上可以达成列表的顺序循环播放,且SeekBar同步更新也可以实现。用户也可以滑动SeekBar来进行歌曲进度的调整。每次打开软件时,都可以正确且快速的扫描本地内存来更新歌曲列表以达到歌曲的增加与删除。在点击ListView的Item时可以实现音频的暂停、播放以及切歌,使用户的操作更加便利,更加人性化;除了使用这个方法来进行切歌之外,还有使用固定按键来进行上述操作。
且在设置播放模式后会按照对应模式进行切歌,如单曲循环状态下,则只会重复播放设置时播放的歌曲,随机播放时则会按照一定的规律进行路径的更改,当音频数量较多时给用户呈现出的效果就是随机播放。
在实现功能过程中,曾碰到了许多问题,以下是主要问题的总结以及相关分析、解决方法:
启动页在真机上无法展示,但是在模拟器上运行良好——安卓版本问题启动页在主题上设置后能进行显示但是应用界面的背景也随之改变——我在主题里面设置windowBackground后虽然可以有启动页的效果,但是当我拆分为两个主题样式时,就无法显示出来了,后来自己突然想到了样式的优先级大于主题,只需在主页面的Layout文件中设置一个backgroud来进行样式的设置,这个问题得以解决。无法扫描本地sdcard,获取音乐列表——由于是在清单文件中静态申请权限,而安卓6.0及以上版本有一个安全机制是需要自己手动在手机上打开存储权限,所以导致在安卓6.0及以上版本运行应用时无法获取音频,分析LogCat后找到了错误,并通过在手机上手动授权得以解决问题。第一次播放以及暂停后播放时seekbar没有移动——调试后发现在第一次播放或暂停后播放的时候mediaPlayer.isPlaying()为false,所以导致seekBar无法正常更新,针对这一问题对mediaPlayer.isPlaying()进行判断,分情况对time进行赋值。播放第一首歌切上一首时会同时播放最后一首和第一首歌——在经过调试后,发现在播放第一首歌时进行切歌会跳转到front()函数,而在此函数中又会获得一个新的播放路径来代替目前的播放路径,使正播放的MediaPlayer没有被暂停销毁从而脱离控制,后来设置了boolean flag = false来记录当前播放文件是否为第一首或最后一首,并在next()和front()函数中加入对flag的判断,如果flag为true,则调用mediaPlayer.stop()来销毁旧对象。小结与心得体会两周课设结束,最终成品总的来说还是让自己有所收获,尤其是在最初的版本到最后的成品的变化中让我对于软件设计这一个词汇有了更新更深的认识。
在设计过程中我曾利用了BottomNavigationActivity进行布局,但在具体实现过程中发现目前我能实现的功能无法很好的支撑起这个布局,显得应用布局太大、内容太空。当我自己使用应用时体验感并不是很好,很多功能显的非常鸡肋且多余,比如在最初我设计了一个用户登录、修改密码及用户信息的功能,但是真正使用起来,给我最直观的感受就是多余且麻烦,所以在最后确定布局时我选择了以简单的一个界面进行展示,虽然布局更为简洁,但体验感却大大提高了,并且可扩展性更高。
这次课设让我认识到真正好的软件产品并不是什么功能都必须要有,只有当产品功能最少但又能满足用户大部分需求时,这才是产品的最佳状态。其余的功能都是在这个状态的基础上进行添加以满足少数需求。
当然这次课设我完成的任务只是音乐播放器最基本的一些功能,在课设结束后我也依旧可以以这个版本为基础进行功能的扩展,比如说同步歌词、专辑照片,还有均衡器的调整等,这些都是一个个关卡在等着我去解决。
所以总的来说这次课设让我受益匪浅,在一定程度上丰富了我的开发经验,并且提高了我独立解决问题的能力,有许多问题是我在自己上手开发一款软件之前从未考虑到或想到的,比如说安卓版本问题对于应用的性能展示的影响。同时也让我对于布局、自定义线程、MediaPlayer的使用以及SeekBar同步更新的了解更加全面具体,并改变了我对于软件设计的看法与认识。