本文將引導大家做一個音樂播放器,在做這個Android開發實例的過程中,能夠幫助大家進一步熟悉和掌握學過的ListView和其他一些組件。爲了有更好的學習效果,其中很多功能我們手動實現,例如音樂播放的快進快退等。
先欣賞下本實例完成後運行的界面效果:
首先我們建立項目,我使用的SDK是Android2.2的,然後在XML中進行佈局。
上方是一個ListView用來顯示我們的音樂列表,中間是一個SeekBar可以拖動當前音樂的播放進度,之所以用SeekBar而不用ProgressBar是因爲我們需要音樂的快進快退功能,可以拖動滑桿改變進度;還有一個TextView,用來顯示當前播放歌曲的名字,時長等。最下方就是4個Button了,分別是上一曲,播放(暫停),停止,下一曲。
大家注意儘量不要在佈局中出現直接顯示在界面上的文字內容,我們把這些內容都放在res/values下的strings.xml中,然後分別引用它們,這樣養成良好的習慣,界面與內容分離,方便調試和後期維護等。現在我們的界面如下:
然後我們把File Explorer打開,在eclipse的Window -- Show View -- Other --Android --File Explore。你也可以直接Alt+Shift+Q。
在mnt/sdcard下面,我們放個兩三首歌曲,在虛擬機中暫不支持中文,導入有中文的文件會報錯的。
接着我們創建一個類,做我們播放器的Service類,我就叫MusicService吧,在裏面聲明以下對象:
Java代碼
publicclassMusicService {
privatestaticfinalFile MUSIC_PATH = Environment
.getExternalStorageDirectory();// 找到music存放的路徑。
publicList musicList;// 存放找到的所有mp3的絕對路徑。
publicMediaPlayer player;// 定義多媒體對象
publicintsongNum;// 當前播放的歌曲在List中的下標
publicString songName;// 當前播放的歌曲名
}
然後我們去加載剛纔添加的MP3文件吧,這裏的方式多種多樣,我隨便寫一個簡單的了:
Java代碼
classMusicFilterimplementsFilenameFilter {
publicbooleanaccept(File dir, String name) {
return(name.endsWith(".mp3"));//返回當前目錄所有以.mp3結尾的文件
}
}
在MusicService類的無參構造函數中實例化對象,並把這些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());
}
}
}
我們寫個方法,來設置當前播放歌曲的名字:(個人覺得這方法比較笨,但暫時沒想到別的辦法)
Java代碼
publicvoidsetPlayName(String dataSource) {
File file =newFile(dataSource);//假設爲D:\\mm.mp3
String name = file.getName();//name=mm.mp3
intindex = name.lastIndexOf(".");//找到最後一個.
songName = name.substring(0, index);//截取爲mm
}
接下來就是我們Service類的基本方法了,也就是開始、暫停、停止、上一首和下一首。
我們分別使用聲明的多媒體對象的start、pause、stop等方法可以完成。
Java代碼
publicvoidstart() {
try{
player.reset();//重置多媒體
String dataSource = musicList.get(songNum);//得到當前播放音樂的路徑
setPlayName(dataSource);//截取歌名
player.setDataSource(dataSource);//爲多媒體對象設置播放路徑
player.prepare();//準備播放
player.start();//開始播放
//setOnCompletionListener 噹噹前多媒體對象播放完成時發生的事件
player.setOnCompletionListener(newOnCompletionListener() {
publicvoidonCompletion(MediaPlayer arg0) {
next();//如果當前歌曲播放完畢,自動播放下一首.
}
});
}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類就寫完了,接着我們去Activity中爲各控件綁定事件。
在這個Activity中,最難做的一點應該就是拖動SeekBar的滑桿改變播放進度了,這裏我考慮再三,用了一個Handler類來處理。
Handler在android裏負責發送和處理消息。它的主要用途有:
1.按計劃發送消息或執行某個Runnanble(使用POST方法)。
2.從其他線程中發送來的消息放入消息隊列中,避免線程衝突(常見於更新UI線程)。
默認情況下,Handler接受的是當前線程下的消息循環實例(使用Handler(Looper looper)、Handler(Looper looper, Handler.Callback callback)可以指定線程),同時一個消息隊列可以被當前線程中的多個對象進行分發、處理(在UI線程中,系統已經有一個Activity來處理了,你可以再起若干個Handler來處理)。在實例化Handler的時候,Looper可以是任意線程的,只要有Handler的指針,任何線程也都可以sendMessage。Handler對於Message的處理不是併發的。一個Looper 只有處理完一條Message纔會讀取下一條,所以消息的處理是阻塞形式的(handleMessage()方法裏不應該有耗時操作,可以將耗時操作放在其他線程執行,操作完後發送Message(通過sendMessges方法),然後由handleMessage()更新UI)。
聲明以下變量:
Java代碼
privateButton btnStart, btnStop, btnNext, btnLast;
privateTextView txtInfo;
privateListView listView;
privateSeekBar seekBar;
privateMusicService musicService;
privateMusicHandler musicHandler;// 處理改變進度條事件
privateMusicThread musicThread;// 自動改變進度條的線程
privatebooleanautoChange, manulChange;// 判斷是進度條是自動改變還是手動改變
privatebooleanisPause;// 判斷是從暫停中恢復還是重新播放
如有報錯的可以先註釋掉不用管它,然後在初始化過程中綁定事件。
這是ListView的填充方法:
Java代碼
privatevoidsetListViewAdapter() {
List<map> date =newArrayList<map>();</map</map
for(String path : musicService.musicList) {
Map map =newHashMap();</string, object></string, object>
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的構造函數是:
public SimpleAdapter (Context context, List> data, int resource, String[] from, int[] to);
第一個參數context,是指在哪個Activity中顯示。
第二個參數是一個泛型作爲數據源,而且每一個List中的一行就代表着呈現出來的一行,Map的鍵就是這一行的列名,值也是有列名的。
第三個參數爲資源文件,就是說要加載這個列所需要的視圖資源文件,我直接引用系統內置的資源,如果你想要漂亮的樣式可以自己寫的。
第四個參數是一個String數組,主要是將Map對象中的名稱映射到列名,一一對應。
第五個是將第四個參數的值一一對象的顯示(一一對應)在接下來的int形的id數組中,這個id數組就是Layout的xml文件中命名id形成的唯一的int型標識符。
SeekBar停止拖動後的事件:
Java代碼
publicvoidonStopTrackingTouch(SeekBar seekBar) {// 停止拖動
intprogress = seekBar.getProgress();
if(!autoChange && manulChange) {
intmusicMax = musicService.player.getDuration();//得到該首歌曲最長秒數
intseekBarMax = seekBar.getMax();
musicService.player
.seekTo(musicMax * progress / seekBarMax);//跳到該曲該秒
musicService.pause();
autoChange =true;
manulChange =false;
}
}
MusicHandler類的實現:
Java代碼
classMusicHandlerextendsHandler {
publicMusicHandler() {
}
@Override
publicvoidhandleMessage(Message msg) {
if(autoChange) {
try{
intposition = musicService.player.getCurrentPosition();//得到當前歌曲播放進度(秒)
intmMax = musicService.player.getDuration();//最大秒數
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("播放已經停止");
}
}
}
//設置當前播放的信息
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的實現:
Java代碼
classMusicThreadimplementsRunnable {
@Override
publicvoidrun() {
while(true)
try{
musicHandler.sendMessage(newMessage());
Thread.sleep(1000);// 每間隔1秒發送一次更新消息
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
技術交流QQ羣:364595326