MusicService已经能够接收广播,通过广播接收的内容来做出相应的MediaPlayer对象的处理,包括播放,暂停,停止等,并当MediaPlayer对象的生命周期发生变化的时候,同样通过发送广播,让UI层产生变换。现在后台处理已经写好。下面就来实现前台的Activity。
1.先构建一个RelativeLayout布局,指定一个背景。
2.我的构想是把整个平面分为三部分,第一部分用来调节音量,因为音量调节常用。第二部分是音乐列表。第三部分是音乐控制按钮和音乐进度条。
3.这三部分一步一步的做出来。先做第一部分调节音量的视图。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/relativeLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/bg_mountain"
>
<LinearLayout
android:id="@+id/main_volumeLayout"
android:layout_height="wrap_content"
android:layout_width="fill_parent">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_weight="1"></LinearLayout>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/main_tv_volumeText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="音量:100%"
android:textColor="#ffffffff"
android:textSize="15dp"/>
<SeekBar
android:id="@+id/main_sb_volumebar"
android:layout_width="82dp"
android:layout_height="wrap_content"
android:maxHeight="5dip"
android:minHeight="5dip"
android:progressDrawable="@drawable/seekbar_style"
/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
这一部分先这样布局,以后如果体验不好再重新修改。
第二部分是歌曲列表的布局
<ListView
android:id="@+id/main_listview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/linearLayout1"
android:layout_below="@id/main_volumeLayout"
android:fastScrollEnabled="true"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:background="@drawable/widget_bg"
android:cacheColorHint="#00000000"></ListView>
第三部分是音乐控制的布局。
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dip"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:background="@drawable/widget_bg"
android:orientation="vertical"
>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center">
<ImageButton
android:id="@+id/main_ibtn_pre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dip"
android:background="@drawable/button_previous"/>
<ImageButton
android:id="@+id/main_ibtn_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dip"
android:background="@drawable/button_play" />
<ImageButton
android:id="@+id/main_ibtn_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dip"
android:background="@drawable/button_stop" />
<ImageButton
android:id="@+id/main_ibtn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dip"
android:background="@drawable/button_next" />
</LinearLayout>
<SeekBar
android:id="@+id/main_seekBar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:paddingRight="10dip" />
<RelativeLayout
android:id="@+id/relativeLayout2"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/main_tv_curtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="00:00" />
<TextView
android:id="@+id/main_tv_totaltime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="00:00" />
</RelativeLayout>
</LinearLayout>
这样整个UI的布局就完成了。
先实现最基础的功能,让音乐播放器能够播放,暂停,下一首,上一首,停止。要实现这个功能就要思考,怎么样才能让MusicService能够按照我们UI的状态变化来操纵MediaPlayer对象呢?最直观的一点就是,最起码要让我们的按钮都有响应了,所以要为我们的音乐控制按钮都加上事件监听器,比如,播放按钮,如果触发了播放按钮,就应该让监听器监听到播放按钮被按下,然后我们需要做的就是在用户按下按钮之后,在监听器下做出相应的相应。MusicService里的MediaPlayer对象有绑定了广播接收器。我们可以让按钮按下之后分发相应的广播。通过广播来通知Service。所以,要给各个按钮增加事件监听器,并分发广播。
MainActivity.java
package com.zharma.greatlovemusic;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import com.zharma.data.Music;
import com.zharma.data.MusicList;
import android.support.v7.app.ActionBarActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
public class MainActivity extends ActionBarActivity {
// 显示组件
private TextView tv_current_time;
private TextView tv_total_time;
private ImageButton imgBtn_Previous;
private ImageButton imgBtn_PlayOrPause;
private ImageButton imgBtn_Stop;
private ImageButton imgBtn_Next;
private SeekBar seekBar;
private ListView listView;
private RelativeLayout root_Layout;
// 当前歌曲的持续时间和当前位置,作用于进度条
private int total_time;
private int curent_time;
//当前歌曲的序号,下标从零开始
private int number;
// 播放状态标志位
private int status;
//歌曲列表对象
private ArrayList<Music> musicArrayList;
//音量控制
private TextView tv_vol;
private SeekBar seekbar_vol;
// 进度条控制常量
private static final int PROGRESS_INCREASE = 0;
private static final int PROGRESS_PAUSE = 1;
private static final int PROGRESS_RESET = 2;
// 更新进度条的Handler
private Handler seekBarHandler;
//睡眠模式相关组件,标识常量
private ImageView iv_sleep;
private Timer timer_sleep ;
private static final boolean NOTSLEEP = false;
private static final boolean ISSLEEP = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
initMusicList();
initListView();
registerListeners();
checkMusicfile();
startService(new Intent(this, MusicService.class));
// 绑定广播接收器,可以接收广播
bindStatusChangedReceiver();
sendBroadcastOnCommand(MusicService.COMMAND_CHECK_IS_PLAYING);
//初始化进度条的Handler
initSeekBarHandler();
status = MusicService.COMMAND_STOP;
}
void findViews() {
listView = (ListView) findViewById(R.id.main_listview);
tv_current_time = (TextView) findViewById(R.id.main_tv_curtime);
tv_total_time = (TextView) findViewById(R.id.main_tv_totaltime);
imgBtn_Previous = (ImageButton) findViewById(R.id.main_ibtn_pre);
imgBtn_PlayOrPause = (ImageButton) findViewById(R.id.main_ibtn_play);
imgBtn_Previous = (ImageButton) findViewById(R.id.main_ibtn_pre);
imgBtn_Next = (ImageButton) findViewById(R.id.main_ibtn_next);
imgBtn_Stop = (ImageButton) findViewById(R.id.main_ibtn_stop);
seekBar = (SeekBar) findViewById(R.id.main_seekBar);
root_Layout = (RelativeLayout) findViewById(R.id.relativeLayout1);
tv_vol = (TextView)findViewById(R.id.main_tv_volumeText);
seekbar_vol = (SeekBar)findViewById(R.id.main_sb_volumebar);
iv_sleep = (ImageView)findViewById(R.id.main_iv_sleep);
}
/**初始化音乐列表对象*/
private void initMusicList() {
musicArrayList = MusicList.getMusicList();
//避免重复添加音乐
if(musicArrayList.isEmpty())
{
Cursor mMusicCursor = this.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.AudioColumns.TITLE);
int indexTitle = mMusicCursor.getColumnIndex(MediaStore.Audio.AudioColumns.TITLE);
int indexArtist = mMusicCursor.getColumnIndex(MediaStore.Audio.AudioColumns.ARTIST);
int indexTotalTime = mMusicCursor.getColumnIndex(MediaStore.Audio.AudioColumns.DURATION);
int indexPath = mMusicCursor.getColumnIndex(MediaStore.Audio.AudioColumns.DATA);
/**通过mMusicCursor游标遍历数据库,并将Music类对象加载带ArrayList中*/
for (mMusicCursor.moveToFirst(); !mMusicCursor.isAfterLast(); mMusicCursor
.moveToNext()) {
String strTitle = mMusicCursor.getString(indexTitle);
String strArtist = mMusicCursor.getString(indexArtist);
String strTotoalTime = mMusicCursor.getString(indexTotalTime);
String strPath = mMusicCursor.getString(indexPath);
if (strArtist.equals("<unknown>"))
strArtist = "无艺术家";
Music music = new Music(strTitle, strArtist, strPath, strTotoalTime);
musicArrayList.add(music);
}
}
}
/**设置适配器并初始化listView*/
private void initListView() {
List<Map<String, String>> list_map = new ArrayList<Map<String, String>>();
HashMap<String, String> map;
SimpleAdapter simpleAdapter;
for (Music music : musicArrayList) {
map = new HashMap<String, String>();
map.put("musicName", music.getMusicName());
map.put("musicArtist", music.getMusicArtist());
list_map.add(map);
}
String[] from = new String[] { "musicName", "musicArtist" };
int[] to = { R.id.listview_tv_title_item, R.id.listview_tv_artist_item };
simpleAdapter = new SimpleAdapter(this, list_map, R.layout.listview,from, to);
listView.setAdapter(simpleAdapter);
}
private void registerListeners() {
imgBtn_Previous.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
sendBroadcastOnCommand(MusicService.COMMAND_PREVIOUS);
}
});
imgBtn_PlayOrPause.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
switch (status) {
case MusicService.STATUS_PLAYING:
sendBroadcastOnCommand(MusicService.COMMAND_PAUSE);
break;
case MusicService.STATUS_PAUSED:
sendBroadcastOnCommand(MusicService.COMMAND_RESUME);
break;
case MusicService.COMMAND_STOP:
sendBroadcastOnCommand(MusicService.COMMAND_PLAY);
break;
default:
break;
}
}
});
imgBtn_Stop.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
sendBroadcastOnCommand(MusicService.COMMAND_STOP);
}
});
imgBtn_Next.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
sendBroadcastOnCommand(MusicService.COMMAND_NEXT);
}
});
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
number = position;
sendBroadcastOnCommand(MusicService.COMMAND_PLAY);
}
});
seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (status == MusicService.STATUS_PLAYING) {
// 发送广播给MusicService,执行跳转
sendBroadcastOnCommand(MusicService.COMMAND_SEEK_TO);
// 进度条恢复移动
seekBarHandler.sendEmptyMessageDelayed(PROGRESS_INCREASE,
1000);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// 进度条暂停移动
seekBarHandler.sendEmptyMessage(PROGRESS_PAUSE);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
if (status != MusicService.STATUS_STOPPED) {
curent_time = progress;
// 更新文本
tv_current_time.setText(formatTime(curent_time));
}
}
});
}
}
现在主体已经写好,后面就是具体的各个实现方法。把发送广播的方法与格式化时间的方法实现如下:
/** 发送命令,控制音乐播放。参数定义在MusicService类中 */
private void sendBroadcastOnCommand(int command) {
Intent intent = new Intent(MusicService.BROADCAST_MUSICSERVICE_CONTROL);
intent.putExtra("command", command);
// 根据不同命令,封装不同的数据
switch (command) {
case MusicService.COMMAND_PLAY:
intent.putExtra("number", number);
break;
case MusicService.COMMAND_SEEK_TO:
intent.putExtra("time", curent_time);
break;
case MusicService.COMMAND_PREVIOUS:
case MusicService.COMMAND_NEXT:
case MusicService.COMMAND_PAUSE:
case MusicService.COMMAND_STOP:
case MusicService.COMMAND_RESUME:
default:
break;
}
sendBroadcast(intent);
}
/**如果列表没有歌曲,则播放按钮不可用,并提醒用户*/
private void checkMusicfile()
{
if (musicArrayList.isEmpty()) {
imgBtn_Next.setEnabled(false);
imgBtn_PlayOrPause.setEnabled(false);
imgBtn_Previous.setEnabled(false);
imgBtn_Stop.setEnabled(false);
Toast.makeText(getApplicationContext(), "当前没有歌曲文件",Toast.LENGTH_SHORT).show();
} else {
imgBtn_Next.setEnabled(true);
imgBtn_PlayOrPause.setEnabled(true);
imgBtn_Previous.setEnabled(true);
imgBtn_Stop.setEnabled(true);
}
}
/** 绑定广播接收器 */
private void bindStatusChangedReceiver() {
receiver = new StatusChangedReceiver();
IntentFilter filter = new IntentFilter(
MusicService.BROADCAST_MUSICSERVICE_UPDATE_STATUS);
registerReceiver(receiver, filter);
}
/** 内部类,用于播放器状态更新的接收广播 */
class StatusChangedReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
// 获取播放器状态
status = intent.getIntExtra("status", -1);
switch (status) {
case MusicService.STATUS_PLAYING:
String musicName = intent.getStringExtra("musicName");
String musicArtist = intent.getStringExtra("musicArtist");
seekBarHandler.removeMessages(PROGRESS_INCREASE);
curent_time = intent.getIntExtra("time", 0);
total_time = intent.getIntExtra("duration", 0);
number = intent.getIntExtra("number", number);
listView.setSelection(number);
seekBar.setProgress(curent_time);
seekBar.setMax(total_time);
seekBarHandler.sendEmptyMessageDelayed(PROGRESS_INCREASE, 1000);
tv_total_time.setText(formatTime(total_time));
imgBtn_PlayOrPause.setBackgroundResource(R.drawable.pause);
// 设置Activity的标题栏文字,提示正在播放的歌曲
MainActivity.this.setTitle("正在播放:" + musicName + " "+ musicArtist);
break;
case MusicService.STATUS_PAUSED:
seekBarHandler.sendEmptyMessage(PROGRESS_PAUSE);
String string = MainActivity.this.getTitle().toString().replace("正在播放:", "已暂停:");
MainActivity.this.setTitle(string);
imgBtn_PlayOrPause.setBackgroundResource(R.drawable.play);
break;
case MusicService.STATUS_STOPPED:
curent_time = 0;
total_time = 0;
tv_current_time.setText(formatTime(curent_time));
tv_total_time.setText(formatTime(total_time));
seekBarHandler.sendEmptyMessage(PROGRESS_RESET);
MainActivity.this.setTitle("GracePlayer");
imgBtn_PlayOrPause.setBackgroundResource(R.drawable.play);
break;
case MusicService.STATUS_COMPLETED:
number = intent.getIntExtra("number", 0);
//顺序模式:到达列表末端时发送停止命令,否则播放下一首
if(playmode == MainActivity.MODE_LIST_SEQUENCE)
{
if(number == MusicList.getMusicList().size()-1)
sendBroadcastOnCommand(MusicService.STATUS_STOPPED);
else
sendBroadcastOnCommand(MusicService.COMMAND_NEXT);
}
//单曲循环
else if(playmode == MainActivity.MODE_SINGLE_CYCLE)
sendBroadcastOnCommand(MusicService.COMMAND_PLAY);
//列表循环:到达列表末端时,把要播放的音乐设置为第一首
else if(playmode == MainActivity.MODE_LIST_CYCLE)
{
//然后发送播放命令。
if(number == musicArrayList.size()-1)
{
number = 0;
sendBroadcastOnCommand(MusicService.COMMAND_PLAY);
}
else sendBroadcastOnCommand(MusicService.COMMAND_NEXT);
}
//随机播放
else if (playmode == MainActivity.MODE_LIST_RANDOM)
{
Random random = new Random();
int randomnum = random.nextInt(listView.getCount());
number = randomnum;
sendBroadcastOnCommand(MusicService.COMMAND_PLAY);
}
seekBarHandler.sendEmptyMessage(PROGRESS_RESET);
MainActivity.this.setTitle("GracePlayer");
imgBtn_PlayOrPause.setBackgroundResource(R.drawable.play);
break;
default:
break;
}
}
}
private void initSeekBarHandler() {
seekBarHandler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case PROGRESS_INCREASE:
if (seekBar.getProgress() < total_time) {
// 进度条前进1秒
seekBar.incrementProgressBy(1000);
seekBarHandler.sendEmptyMessageDelayed(
PROGRESS_INCREASE, 1000);
// 修改显示当前进度的文本
tv_current_time.setText(formatTime(curent_time));
curent_time += 1000;
}
break;
case PROGRESS_PAUSE:
seekBarHandler.removeMessages(PROGRESS_INCREASE);
break;
case PROGRESS_RESET:
// 重置进度条界面
seekBarHandler.removeMessages(PROGRESS_INCREASE);
seekBar.setProgress(0);
tv_current_time.setText("00:00");
break;
}
}
};
}
最后还要把我们的Service加到配置文件里。
<service
android:name="com.zharma.greatlovemusic.MusicService"
android:exported="true" >
<intent-filter>
<action android:name="VideoService.START_Video_SERVICE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
到目前为止,这个播放器已经实现了基本的音乐播放功能,后面有时间就会再加一些网络歌词获取,放到一个Activity里。搞个华丽的侧滑界面。弄个睡眠模式,播放模式之类的东西,让这个播放器看起来更正儿八经。