习惯于在做事情的时候听一些熟悉的歌,你也许会说,这是一心二用,但有的时候却反而更能让人投入自己的工作中。至少对于我来说是这样,当然他人也许无法理解。或许是为了给自己找个正当的理由听音乐,所以做了这个安卓手机的播放器。在此之前由于搭档有意向的是另一个方面,于是在商量之后,我们决定将二者联合起来。当然,这里写的也许不算严格意义上的通信项目,播放器只是项目的一部分而已。下面是播放器的实现过程。
这里利用Android jdk中的MediaPlayer这个类来实现。
先看布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/title"
/>
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<ImageView
android:layout_x="7dip"
android:layout_y="17dip"
android:layout_width="198dip"
android:layout_height="198dip"
android:background="#ffffff"
/>
<Button
android:layout_x="230dip"
android:layout_y="125dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="playlist"
android:id="@+id/playlist"
/>
<Button
android:layout_x="230dip"
android:layout_y="70dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="choose"
android:id="@+id/choose"
/>
<LinearLayout
android:layout_x="3dip"
android:layout_y="230dip"
android:layout_width="fill_parent"
android:layout_height="65dip"
android:orientation="vertical">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/songname"
android:id="@+id/songname"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/record"
android:id="@+id/record"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/artist"
android:id="@+id/artist"
/>
</LinearLayout>
</AbsoluteLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:gravity="center_horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start"
android:id="@+id/start_pause"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="stop"
android:id="@+id/stop"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="next"
android:id="@+id/next"
/>
</LinearLayout>
<SeekBar android:id="@+id/seekbar"
android:layout_width="300dip"
android:layout_height="20px"
android:layout_gravity="center"
/>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:gravity="clip_horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:id="@+id/goTime"
android:text="@string/original_time"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="244dip"
android:id="@+id/totalTime"
android:text="@string/original_time"
/>
</LinearLayout>
</LinearLayout>
在这边简单介绍一下。界面没有什么特别的,正是最为normal的一个播放器界面。上左是一个图片框,右面三个按钮。下方三排TextView用于显示歌曲信息,再下面是控制播放的按钮和进度条。
(这里说明一下,图片暂用白色背景代替,音乐播放总时间未获得到是由于本人系统重装,模拟器里的sd卡中无内容导致,与下面的方法无关。)
下面是主界面的activity:
package lsy.mp20120211;
import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
public class MainPanel extends Activity implements Runnable {
private MediaPlayer mp1 = new MediaPlayer();// 新建的一個播放器對象
private Button start_pause, stop, next;
private TextView goTime, totalTime, song, album, artist;
private SeekBar seek;// 進度條
private String[] total_tim = new String[2];// 這兩項和時間顯示有關
private String[] current_tim = new String[2];
private int t, i = 0;
private android.os.Handler handler = new android.os.Handler();// 控制線程的Handler
private boolean userChangeSeek;
private boolean isStop = true;
private mainhandler mhandler = new mainhandler();
private final int CHANGE_SONG = 1;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/** 得到各組件 */
seek = (SeekBar) this.findViewById(R.id.seekbar);
goTime = (TextView) this.findViewById(R.id.goTime);
totalTime = (TextView) this.findViewById(R.id.totalTime);
song = (TextView) this.findViewById(R.id.songname);
album = (TextView) this.findViewById(R.id.record);
artist = (TextView) this.findViewById(R.id.artist);
start_pause = (Button) this.findViewById(R.id.start_pause);
stop = (Button) this.findViewById(R.id.stop);
next = (Button) this.findViewById(R.id.next);
// 加载音乐队列
Tools.addSongs();
System.out.println("队列里的音乐个数为:" + Tools.songlist.size());
// 啟動新建音樂的線程
Thread tt = new Thread(this);
tt.start();
// 給按鈕添加監聽器
start_pause.setOnClickListener(oc1);
stop.setOnClickListener(oc2);
next.setOnClickListener(oc3);
// 進度條拖動的監聽器
seek.setOnSeekBarChangeListener(osl);
}
/** 程序運行的run方法 */
public void run() {
while (i < Tools.songlist.size()) {
if (isStop) {
System.out.println("停止播放" + isStop);
mhandler.sendEmptyMessage(CHANGE_SONG);
newPlayer(Tools.songlist.get(i).getUrl());
System.out.println("完成一遍" + i);
}
}
}
/**
* 新建一個播放器的操作
*/
public void newPlayer(String url) {
// 新建一個播放器
isStop = false;
try {
//mp1.reset();
mp1.setDataSource(url);
mp1.prepare();
// 得到播放總时间(单位:毫秒)
t = mp1.getDuration();
System.out.println("t=" + t);
// 轉成時間格式
total_tim = changeInt2Time(t);
// 顯示總時間
String total_time = total_tim[0] + ":" +total_tim[1];
totalTime.setText(total_time);
// 如果直接下一首则不用点击直接播放
if (start_pause.getText().equals("pause")) {
// mp1.prepare();
mp1.start();
System.out.println("音乐播放中..." + mp1.isPlaying());
}
mp1.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer arg0) {
seek.setProgress(0);
if (Tools.songlist.size() == 0) {
Toast.makeText(MainPanel.this, "结束", 1000).show();
mp1.release();
} else {
isStop = true;
}
}
});
} catch (Exception e) {
System.out.println("找不到文件!!!");
e.printStackTrace();
}
}
这上面只是一部分。在这里要说的是,用 MediaPlayer.setDataSource(url)的方法来创建播放歌曲时,需执行MediaPlayer.prepare()方法,才能使MediaPlayer.getDuration()方法得到正确的音乐播放时间(未prepare时,该方法也能得到一个long型的整数,但是相对来说很大,这里不是很清楚得到的是什么值);而通过MediaPlayer.create()方法创建则不需要。
下面要说的是控制播放按钮的监听器:
/** 開始和暫停的监听 */
OnClickListener oc1 = new OnClickListener() {
public void onClick(View v) {
try {
if (start_pause.getText().equals("start")) {
if (mp1 != null) {
mp1.stop();
}
// 計時
seek.setMax(t);
handler.post(time_thread);
// 播放
mp1.prepare();
mp1.start();
start_pause.setText("pause");
System.out.println("音乐播放中..." + mp1.isPlaying());
} else if (start_pause.getText().equals("pause")) {
// 暫停
mp1.stop();
start_pause.setText("start");
handler.removeCallbacks(time_thread);
System.out.println("暂停" + mp1.isLooping());
}
} catch (Exception e) {
System.out.println("播放发生异常...");
e.printStackTrace();
}
}
};
/** 停止按鈕的監聽 */
OnClickListener oc2 = new OnClickListener() {
public void onClick(View v) {
mp1.stop();
handler.removeCallbacks(time_thread);
seek.setProgress(0);
isStop = true;
goTime.setText("00:00");
start_pause.setText("start");
}
};
/** 下一首按鈕的監聽 */
OnClickListener oc3 = new OnClickListener() {
public void onClick(View v) {
mp1.stop();
seek.setProgress(0);
goTime.setText("00:00");
i = 1;
isStop = true;
}
};
播放过程中需要另一个线程来同步控制进度条的移动,这里用安卓特有的Handler来控制。
/** 時間進度條的線程 */
private Runnable time_thread = new Runnable() {
public void run() {
// 設置進度
int current = seek.getProgress() + 1000;
seek.setProgress(current);
// 設置目前播放時間
int cur_time = mp1.getCurrentPosition();
// 轉換格式使之顯示在介面上
current_tim = changeInt2Time(cur_time);
goTime.setText(current_tim[0] + ":" + current_tim[1]);
// 控制線程
handler.postDelayed(time_thread, 1000);
}
};
此外,当玩家拖动进度条时,相应的歌曲进度也将随之匹配变化。
/** 拖動進度條時對進度條的監聽 */
OnSeekBarChangeListener osl = new OnSeekBarChangeListener() {
// 進度改變時的動作
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
fromUser = userChangeSeek;
// 設置音樂播放與進度匹配
mp1.seekTo(progress);
current_tim = changeInt2Time(progress);
goTime.setText(current_tim[0] + ":" + current_tim[1]);
}
// 觸摸進度條時的監聽
public void onStartTrackingTouch(SeekBar seekBar) {
}
// 停止觸摸時的監聽
public void onStopTrackingTouch(SeekBar seekBar) {
userChangeSeek = true;
}
};
最后,是一个刷新主线程UI的Handler。Android与普通java工程不同之处的在于,对于一个activity,它自带一个管理主界面的runnable,因此不允许其他的子线程随意刷新主界面。如果你尝试在自定义的线程中刷新界面,则系统会给你报出CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views这个错误。于是我们重新写了handleMessage的方法。
/** 新建一个主线程中的handler */
private class mainhandler extends android.os.Handler {
public void handleMessage(Message msg) {
super.handleMessage(msg);
int what = msg.what;
switch (what) {
case CHANGE_SONG: {
// 切換下一首歌曲時,用來刷新主界面UI
song.setText(" 歌曲: " + Tools.songlist.get(i).getName());
album.setText(" 专辑: " + Tools.songlist.get(i).getAlbum());
artist.setText("艺术家: " + Tools.songlist.get(i).getArtist());
}
}
}
}
这里还有一个无关紧要的方法,也就是把时间的毫秒形式转换成我们习惯的格式。
/**
* 把時間的毫秒形式改成xx:xx
*
* @param t
*/
public String[] changeInt2Time(int t) {
int s, min = 0;
String[] time = new String[2];
String minutes, seconds;
s = t / 1000;
if (s >= 60) {
min = s / 60;
s = s % 60;
}
// 將分秒轉為字符串
if (s < 10) {
seconds = "0" + s;
} else {
seconds = s + "";
}
if (min < 10) {
minutes = "0" + min;
} else {
minutes = min + "";
}
time[0] = minutes;
time[1] = seconds;
return time;
}
到了这里,一个简单的播放器就完成了。这里仅仅是整个项目的一部分,此外还有播放列表和歌曲的选择以及与电脑通信中歌曲的下载等等,这里暂时不写了。