Android音樂播放器的開發(fā)實(shí)例

本文將引導(dǎo)大家做一個(gè)音樂播放器,在做這個(gè)Android開發(fā)實(shí)例的過程中,能夠幫助大家進(jìn)一步熟悉和掌握學(xué)過的ListView和其他一些組件。為了有更好的學(xué)習(xí)效果,其中很多功能我們手動(dòng)實(shí)現(xiàn),例如音樂播放的快進(jìn)快退等。

先欣賞下本實(shí)例完成后運(yùn)行的界面效果:

首先我們建立項(xiàng)目,我使用的SDK是Android2.2的,然后在XML中進(jìn)行布局。

上方是一個(gè)ListView用來顯示我們的音樂列表,中間是一個(gè)SeekBar可以拖動(dòng)當(dāng)前音樂的播放進(jìn)度,之所以用SeekBar而不用ProgressBar是因?yàn)槲覀冃枰魳返目爝M(jìn)快退功能,可以拖動(dòng)滑桿改變進(jìn)度;還有一個(gè)TextView,用來顯示當(dāng)前播放歌曲的名字,時(shí)長等。最下方就是4個(gè)Button了,分別是上一曲,播放(暫停),停止,下一曲。

大家注意盡量不要在布局中出現(xiàn)直接顯示在界面上的文字內(nèi)容,我們把這些內(nèi)容都放在res/values下的strings.xml中,然后分別引用它們,這樣養(yǎng)成良好的習(xí)慣,界面與內(nèi)容分離,方便調(diào)試和后期維護(hù)等?,F(xiàn)在我們的界面如下:

然后我們把File Explorer打開,在eclipse的Window -- Show View -- Other --Android --File Explore。你也可以直接Alt+Shift+Q。

在mnt/sdcard下面,我們放個(gè)兩三首歌曲,在虛擬機(jī)中暫不支持中文,導(dǎo)入有中文的文件會(huì)報(bào)錯(cuò)的。

接著我們創(chuàng)建一個(gè)類,做我們播放器的Service類,我就叫MusicService吧,在里面聲明以下對象:

Java代碼

publicclassMusicService?{

privatestaticfinalFile?MUSIC_PATH?=?Environment

.getExternalStorageDirectory();//?找到music存放的路徑。

publicList?musicList;//?存放找到的所有mp3的絕對路徑。

publicMediaPlayer?player;//?定義多媒體對象

publicintsongNum;//?當(dāng)前播放的歌曲在List中的下標(biāo)

publicString?songName;//?當(dāng)前播放的歌曲名

}

然后我們?nèi)ゼ虞d剛才添加的MP3文件吧,這里的方式多種多樣,我隨便寫一個(gè)簡單的了:

Java代碼

classMusicFilterimplementsFilenameFilter?{

publicbooleanaccept(File?dir,?String?name)?{

return(name.endsWith(".mp3"));//返回當(dāng)前目錄所有以.mp3結(jié)尾的文件

}

}

在MusicService類的無參構(gòu)造函數(shù)中實(shí)例化對象,并把這些MP3文件放到musicList中。

Java代碼

publicMusicService()?{

musicList?=newArrayList();

player?=newMediaPlayer();

if(MUSIC_PATH.listFiles(newMusicFilter()).length?>0)?{

for(File?file?:?MUSIC_PATH.listFiles(newMusicFilter()))?{

musicList.add(file.getAbsolutePath());

}

}

}

我們寫個(gè)方法,來設(shè)置當(dāng)前播放歌曲的名字:(個(gè)人覺得這方法比較笨,但暫時(shí)沒想到別的辦法)

Java代碼

publicvoidsetPlayName(String?dataSource)?{

File?file?=newFile(dataSource);//假設(shè)為D:\\mm.mp3

String?name?=?file.getName();//name=mm.mp3

intindex?=?name.lastIndexOf(".");//找到最后一個(gè).

songName?=?name.substring(0,?index);//截取為mm

}

接下來就是我們Service類的基本方法了,也就是開始、暫停、停止、上一首和下一首。

我們分別使用聲明的多媒體對象的start、pause、stop等方法可以完成。

Java代碼

publicvoidstart()?{

try{

player.reset();//重置多媒體

String?dataSource?=?musicList.get(songNum);//得到當(dāng)前播放音樂的路徑

setPlayName(dataSource);//截取歌名

player.setDataSource(dataSource);//為多媒體對象設(shè)置播放路徑

player.prepare();//準(zhǔn)備播放

player.start();//開始播放

//setOnCompletionListener?當(dāng)當(dāng)前多媒體對象播放完成時(shí)發(fā)生的事件

player.setOnCompletionListener(newOnCompletionListener()?{

publicvoidonCompletion(MediaPlayer?arg0)?{

next();//如果當(dāng)前歌曲播放完畢,自動(dòng)播放下一首.

}

});

}catch(Exception?e)?{

Log.v("MusicService",?e.getMessage());

}

}

publicvoidnext()?{

songNum?=?songNum?==?musicList.size()?-1?0:?songNum?+1;

start();

}

publicvoidlast()?{

songNum?=?songNum?==0??musicList.size()?-1:?songNum?-1;

start();

}

publicvoidpause()?{

if(player.isPlaying())

player.pause();

else

player.start();

}

publicvoidstop()?{

if(player.isPlaying())?{

player.stop();

}

}

到此為止我們的Service類就寫完了,接著我們?nèi)ctivity中為各控件綁定事件。

在這個(gè)Activity中,最難做的一點(diǎn)應(yīng)該就是拖動(dòng)SeekBar的滑桿改變播放進(jìn)度了,這里我考慮再三,用了一個(gè)Handler類來處理。

Handler在android里負(fù)責(zé)發(fā)送和處理消息。它的主要用途有:

1.按計(jì)劃發(fā)送消息或執(zhí)行某個(gè)Runnanble(使用POST方法)。

2.從其他線程中發(fā)送來的消息放入消息隊(duì)列中,避免線程沖突(常見于更新UI線程)。

默認(rèn)情況下,Handler接受的是當(dāng)前線程下的消息循環(huán)實(shí)例(使用Handler(Looper looper)、Handler(Looper looper, Handler.Callback callback)可以指定線程),同時(shí)一個(gè)消息隊(duì)列可以被當(dāng)前線程中的多個(gè)對象進(jìn)行分發(fā)、處理(在UI線程中,系統(tǒng)已經(jīng)有一個(gè)Activity來處理了,你可以再起若干個(gè)Handler來處理)。在實(shí)例化Handler的時(shí)候,Looper可以是任意線程的,只要有Handler的指針,任何線程也都可以sendMessage。Handler對于Message的處理不是并發(fā)的。一個(gè)Looper 只有處理完一條Message才會(huì)讀取下一條,所以消息的處理是阻塞形式的(handleMessage()方法里不應(yīng)該有耗時(shí)操作,可以將耗時(shí)操作放在其他線程執(zhí)行,操作完后發(fā)送Message(通過sendMessges方法),然后由handleMessage()更新UI)。

聲明以下變量:

Java代碼

privateButton?btnStart,?btnStop,?btnNext,?btnLast;

privateTextView?txtInfo;

privateListView?listView;

privateSeekBar?seekBar;

privateMusicService?musicService;

privateMusicHandler?musicHandler;//?處理改變進(jìn)度條事件

privateMusicThread?musicThread;//?自動(dòng)改變進(jìn)度條的線程

privatebooleanautoChange,?manulChange;//?判斷是進(jìn)度條是自動(dòng)改變還是手動(dòng)改變

privatebooleanisPause;//?判斷是從暫停中恢復(fù)還是重新播放

如有報(bào)錯(cuò)的可以先注釋掉不用管它,然后在初始化過程中綁定事件。

這是ListView的填充方法:

Java代碼

privatevoidsetListViewAdapter()?{

List>?date?=newArrayList>();

for(String?path?:?musicService.musicList)?{

Map?map?=newHashMap();

File?file?=newFile(path);

map.put("fileName",?file.getName());

date.add(map);

}

SimpleAdapter?adapter?=newSimpleAdapter(this,?date,

android.R.layout.simple_list_item_1,

newString[]?{"fileName"},newint[]?{?android.R.id.text1?});

listView.setAdapter(adapter);

}

SimpleAdapter的構(gòu)造函數(shù)是:

public SimpleAdapter (Context context, List> data, int resource, String[] from, int[] to);

第一個(gè)參數(shù)context,是指在哪個(gè)Activity中顯示。

第二個(gè)參數(shù)是一個(gè)泛型作為數(shù)據(jù)源,而且每一個(gè)List中的一行就代表著呈現(xiàn)出來的一行,Map的鍵就是這一行的列名,值也是有列名的。

第三個(gè)參數(shù)為資源文件,就是說要加載這個(gè)列所需要的視圖資源文件,我直接引用系統(tǒng)內(nèi)置的資源,如果你想要漂亮的樣式可以自己寫的。

第四個(gè)參數(shù)是一個(gè)String數(shù)組,主要是將Map對象中的名稱映射到列名,一一對應(yīng)。

第五個(gè)是將第四個(gè)參數(shù)的值一一對象的顯示(一一對應(yīng))在接下來的int形的id數(shù)組中,這個(gè)id數(shù)組就是Layout的xml文件中命名id形成的唯一的int型標(biāo)識符。

SeekBar停止拖動(dòng)后的事件:

Java代碼

publicvoidonStopTrackingTouch(SeekBar?seekBar)?{//?停止拖動(dòng)

intprogress?=?seekBar.getProgress();

if(!autoChange?&&?manulChange)?{

intmusicMax?=?musicService.player.getDuration();//得到該首歌曲最長秒數(shù)

intseekBarMax?=?seekBar.getMax();

musicService.player

.seekTo(musicMax?*?progress?/?seekBarMax);//跳到該曲該秒

musicService.pause();

autoChange?=true;

manulChange?=false;

}

}

MusicHandler類的實(shí)現(xiàn):

Java代碼

classMusicHandlerextendsHandler?{

publicMusicHandler()?{

}

@Override

publicvoidhandleMessage(Message?msg)?{

if(autoChange)?{

try{

intposition?=?musicService.player.getCurrentPosition();//得到當(dāng)前歌曲播放進(jìn)度(秒)

intmMax?=?musicService.player.getDuration();//最大秒數(shù)

intsMax?=?seekBar.getMax();//seekBar最大值,算百分比

seekBar.setProgress(position?*?sMax?/?mMax);

txtInfo.setText(setPlayInfo(position?/1000,?mMax?/1000));

}catch(Exception?e)?{

e.printStackTrace();

}

}else{

seekBar.setProgress(0);

txtInfo.setText("播放已經(jīng)停止");

}

}

}

//設(shè)置當(dāng)前播放的信息

privateString?setPlayInfo(intposition,intmax)?{

String?info?="正在播放:??"+?musicService.songName?+"\t\t";

//笨辦法?寫完才想起可以用%的,但不想改了

intpMinutes?=0;

while(position?>=60)?{

pMinutes++;

position?-=60;

}

String?now?=?(pMinutes?<10?"0"+?pMinutes?:?pMinutes)?+":"

+?(position?<10?"0"+?position?:?position);

intmMinutes?=0;

while(max?>=60)?{

mMinutes++;

max?-=60;

}

String?all?=?(mMinutes?<10?"0"+?mMinutes?:?mMinutes)?+":"

+?(max?<10?"0"+?max?:?max);

returninfo?+?now?+"?/?"+?all;

}

MusicThread的實(shí)現(xiàn):

Java代碼

classMusicThreadimplementsRunnable?{

@Override

publicvoidrun()?{

while(true)

try{

musicHandler.sendMessage(newMessage());

Thread.sleep(1000);//?每間隔1秒發(fā)送一次更新消息

}catch(InterruptedException?e)?{

e.printStackTrace();

}

}

}

技術(shù)交流QQ群:364595326

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本人初學(xué)Android,最近做了一個(gè)實(shí)現(xiàn)安卓簡單音樂播放功能的播放器,收獲不少,于是便記錄下來自己的思路與知識總結(jié)...
    落日柳風(fēng)閱讀 19,460評論 2 41
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,206評論 25 708
  • Android Handler機(jī)制系列文章整體內(nèi)容如下: Android Handler機(jī)制1之ThreadAnd...
    隔壁老李頭閱讀 8,393評論 8 57
  • 參加這個(gè)活動(dòng),仍然難以采取行動(dòng)。 看書,卻沒有心得。 如咸魚一般。 想起鄧教授的教導(dǎo),不要只是積累,只是擁有,要懂...
    runruan閱讀 119評論 0 0
  • 這世上真有人過著你想要的生活,既能朝九晚五,又能浪跡天涯。浪跡天涯,所有看過金庸古龍小說的人都曾向往那樣的生活,浪...
    木安m閱讀 1,408評論 27 15

友情鏈接更多精彩內(nèi)容