Android小玩意儿-- 从头开发一个正经的MusicPlayer(三)

MusicService已经能够接收广播,通过广播接收的内容来做出相应的MediaPlayer对象的处理,包括播放,暂停,停止等,并当MediaPlayer对象的生命周期发生变化的时候,同样通过发送广播,让UI层产生变换。现在后台处理已经写好。下面就来实现前台的Activity。

1·构建UI布局框架##

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的布局就完成了。

2在之前的MainActivity的基础上继续开发##

先实现最基础的功能,让音乐播放器能够播放,暂停,下一首,上一首,停止。要实现这个功能就要思考,怎么样才能让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;
			}
		}
	};
}

3·AndroidManifest.xml文件配置##

最后还要把我们的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里。搞个华丽的侧滑界面。弄个睡眠模式,播放模式之类的东西,让这个播放器看起来更正儿八经。

原文地址:https://www.cnblogs.com/zharma/p/4576309.html