一、前言:
第一次做安卓的個人項目,還是比較輕鬆的,將平時學的知識用到點子上就好容易,遇到自己想實現的而且還不會的就上網自己查,或者去官網的安卓開發者手冊讀老英文,這一塊是最耗實踐的,我在項目中在添加隨機播放,加入歌曲時間,用什麼數據結構存儲歌曲信息等地方花了很大精力去學習,畢竟網上的東西查到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傳遞一些數據,並且實現了在電話打過來時,停止播放音樂,打完電話繼續播放。還有一個問題,就是查看歌詞的設計能用輪播將歌詞的速度與歌曲的速度同步就好了。