本文將引導(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ù)等√蜕簦現(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吧,在里面聲明以下對(duì)象:
Java代碼
publicclassMusicService?{
privatestaticfinalFile?MUSIC_PATH?=?Environment
.getExternalStorageDirectory();//?找到music存放的路徑慷嗜。
publicList?musicList;//?存放找到的所有mp3的絕對(duì)路徑淀弹。
publicMediaPlayer?player;//?定義多媒體對(duì)象
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í)例化對(duì)象薇溃,并把這些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類的基本方法了,也就是開始堕绩、暫停薄啥、停止、上一首和下一首逛尚。
我們分別使用聲明的多媒體對(duì)象的start、pause刁愿、stop等方法可以完成绰寞。
Java代碼
publicvoidstart()?{
try{
player.reset();//重置多媒體
String?dataSource?=?musicList.get(songNum);//得到當(dāng)前播放音樂的路徑
setPlayName(dataSource);//截取歌名
player.setDataSource(dataSource);//為多媒體對(duì)象設(shè)置播放路徑
player.prepare();//準(zhǔn)備播放
player.start();//開始播放
//setOnCompletionListener?當(dāng)當(dāng)前多媒體對(duì)象播放完成時(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è)對(duì)象進(jìn)行分發(fā)、處理(在UI線程中凿叠,系統(tǒng)已經(jīng)有一個(gè)Activity來處理了涩笤,你可以再起若干個(gè)Handler來處理)。在實(shí)例化Handler的時(shí)候盒件,Looper可以是任意線程的蹬碧,只要有Handler的指針,任何線程也都可以sendMessage炒刁。Handler對(duì)于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對(duì)象中的名稱映射到列名岩瘦,一一對(duì)應(yīng)。
第五個(gè)是將第四個(gè)參數(shù)的值一一對(duì)象的顯示(一一對(duì)應(yīng))在接下來的int形的id數(shù)組中窿撬,這個(gè)id數(shù)組就是Layout的xml文件中命名id形成的唯一的int型標(biāo)識(shí)符启昧。
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