本篇实现音乐播放的功能,利用拖动条,listview以及广播返回消息给活动,更新UI界面。
效果演示:
这里无法听到音乐,这里只关注界面效果吧。
代码分析:
1.创建服务类,播放相应路径下的音乐:
这里音乐的路径是由主活动中的intent中存放的,开启服务时直接传过来即可。
2.注册服务:
这里由于服务类的包和活动的包不是同一个包,所以需要写全称。
3.创建Listview:
创建Listview的适配器:
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
public class MusicAdapter extends BaseAdapter {
private LayoutInflater inflater;
private File[] musics;
private MediaMetadataRetriever mmr ;
public MusicAdapter(LayoutInflater inflater, File[] musics) {
this.inflater = inflater;
this.musics = musics;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return musics.length;
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
@Override
public View getView(int position, View convertView, ViewGroup arg2) {
// TODO Auto-generated method stub
ViewHolder vh = null;
if (convertView == null) {
vh = new ViewHolder();
convertView = inflater.inflate(R.layout.music_item_list, null);
vh.textviewName = (TextView) convertView.findViewById(R.id.textview_music_name);
vh.imgPic = (ImageView) convertView.findViewById(R.id.img_pic);
vh.textviewArtist = (TextView) convertView.findViewById(R.id.textview_artist);
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView.getTag();
}
// 放资源
vh.textviewName.setText(musics[position].getName());
// 放音乐图片和艺术家
mmr = new MediaMetadataRetriever();
mmr.setDataSource(musics[position].getAbsolutePath());
String author = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
if (author != null) {
vh.textviewArtist.setText("艺术家:" + author);
} else {
vh.textviewArtist.setText("艺术家:未知");
}
// 放图片
byte[] imag = mmr.getEmbeddedPicture();// 得到图片
if (imag != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(imag, 0, imag.length);
vh.imgPic.setImageBitmap(bitmap);
} else {
vh.imgPic.setImageResource(R.drawable.den);
}
return convertView;
}
class ViewHolder {
ImageView imgPic;
TextView textviewName;
TextView textviewArtist;
}
}
Listview适配器这里不做详解,但需要注意,获取music中自带的图片和艺术家等自带信息的方法:
要通过MediaMetadataRetriever类:获取其实例,然后设置Source,现在的MediaMetadataRetriever就可以去获得这个指定路径音乐的信息。
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(musics[position].getAbsolutePath());
这里仅介绍了获取图片和艺术家的方法:
获取艺术家名称:mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
获取图片:
mmr.getEmbeddedPicture();//获得的是图片的字节流
Bitmap bitmap = BitmapFactory.decodeByteArray(imag, 0, imag.length);//获得Bitmap形式的图片
即:
创建音乐列表,并加到Listview中:
添加到适配器
4.为Listview创建点击事件:
在listview的点击事件中开启服务,并携带音乐列表相应音乐的路径。
这里同时携带的还有一个type类型,用于服务判断,收到的指示是开始播放音乐,还是跳转音乐到相应位置,添加这个type是因为后面需要拖动seekbar来改变播放位置。
5.服务播放音乐后,将信息传给活动,改变seekBar位置,用广播实现:
活动里创建广播子类接收消息,接收seekbar的最大值及进程位置:
接收到广播,通过type(注:这是另一个type,和之前的不同)来判断接收的是音乐的总时长,还是当前的进度。
动态注册广播:
服务子类发送广播:
由于需要每隔1s通知活动,UI主线程绘制SeekBar,所以发送的音乐播放位置需要在新建的子线程中,【子线程每运行1s,都发送广播,注意主线程接收广播也是已消息队列的形式。】
这里还发送了播放中音乐的总时长,这个发送不用在子线程中进行,因为它不改变,所以只需要在播放音乐后直接发送时长,并启动发送进程位置的子线程就OK。
6.seekBar拖动实现更改音乐播放位置:
对SeekBar添加监听事件,监听位置的改变,注:将代码写到onStopTrackingTouch(SeekBar seekbar)方法中,它在鼠标停止拖动sekbar时被调用。不要放在onProgressChanged方法中,这是只要进度条位置改变,它就调用,而我们在前面是服务中有子线程在返回数据控制进度,进度一定一直在改变,所以onProgressChanged方法会一直调用,结果就会造成一播放就会有两个一前一后的叠音出现。
所有代码:
主活动:
public class MainActivity extends Activity implements OnClickListener {
private ListView mListview;
private SeekBar mBar;
private Button mButtonPlay;
private MusicAdapter musicAdapter;
private File[] musics;
private MusicBroadcast musicbroad;
private TextView textviewDuration;
private TextView textviewCurrent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListview = (ListView) findViewById(R.id.listview_music);
mBar = (SeekBar) findViewById(R.id.progressBar);
mButtonPlay = (Button) findViewById(R.id.button_play);
textviewDuration=(TextView) findViewById(R.id.textview_duration);
textviewCurrent = (TextView) findViewById(R.id.textview_current);
mButtonPlay.setOnClickListener(this);
//注册广播
musicbroad = new MusicBroadcast();
IntentFilter filter = new IntentFilter("music_broadcast_code_action");
registerReceiver(musicbroad, filter);
getMusicList();
musicAdapter = new MusicAdapter(getLayoutInflater(), musics);
mListview.setAdapter(musicAdapter);
mListview.setOnItemClickListener(new OnItemClickListener() {// 对list添加点击事件,点击播放音乐
@Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int positon, long arg3) {
// TODO Auto-generated method stub//启动服务播放对应音乐
Intent intent = new Intent(getApplicationContext(),
MusicService.class);
intent.putExtra("music_path",
musics[positon].getAbsolutePath());// 得到点击音乐的路径
intent.putExtra("type", Config.TYPE_START_MUSIC);
startService(intent);// 启动服务//不要忘了注册,注册,注册
Log.d("message",
"启动:" + musics[positon].getAbsolutePath());
}
});
//为seekbar建立了一个监听事件;
mBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekbar) {//当停止拖动后,调用此方法
// TODO Auto-generated method stub
Intent intent = new Intent(getApplicationContext(),MusicService.class);
intent.putExtra("type", Config.TYPE_CHANG_MUSIC);
intent.putExtra("progress_to", seekbar.getProgress());//得到拖动seekbar的位置
startService(intent);//启动服务
}
@Override
public void onStartTrackingTouch(SeekBar arg0) {//开始拖动时调用
// TODO Auto-generated method stub
}
@Override
public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {//进度改变时就调用
// TODO Auto-generated method stub
}
});
}
public void getMusicList() {
// 获取音乐列表
File sdcard = Environment.getExternalStorageDirectory();// 获得手机sd卡路径
Log.d("music", sdcard + "");
File musicDir = new File(sdcard, "/Music");// 获得存放音乐的目录
musics = musicDir.listFiles();// 获得目录下的所有文件
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.button_play:
break;
default:
break;
}
}
// 建立广播接收音乐服务发送的广播信息
class MusicBroadcast extends BroadcastReceiver {
/**seekbar:必须设置总长度,和setprogress中间进程,才能后正常走动。
* 收到的时间为ms单位:设置成分:秒* */
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
int type = intent.getIntExtra("type", 0);
switch (type) {
case MusicService.TYPE_MUSIC_DURATION://获得的是总时长
int duration = intent.getIntExtra("duration", 0);
int duration_miao = duration/1000;
int duration_minite =duration_miao/60;
Log.d("message_duration", duration+"");
textviewDuration.setText(duration_minite+"分 : "+duration_miao%60+"秒");
mBar.setMax(duration);
break;
case MusicService.TYPE_MUSIC_CURRENT://获得的是当前播放到的时间位置
int current = intent.getIntExtra("current", 0);
Log.d("message_current", current+"");
int current_miao = current/1000;
int current_minite =current_miao/60;
textviewCurrent.setText(current_minite+"分 : "+current_miao%60+"秒");
mBar.setProgress(current);
break;
default:
break;
}
}
}
}
主活动的布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<ListView
android:id="@+id/listview_music"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
></ListView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/textview_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:text="总时长"/>
<TextView
android:id="@+id/textview_current"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:layout_marginLeft="30dp"
android:text="当前时间"/>
</LinearLayout>
<SeekBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/button_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="播放音乐"/>
</LinearLayout>
服务类:
public class MusicService extends Service {
public static final int TYPE_MUSIC_DURATION = 0;// 类型是总时长
public static final int TYPE_MUSIC_CURRENT = 1;// 类型是总时长
private MediaPlayer player;
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
int type = intent.getIntExtra("type", 0);
switch (type) {
case Config.TYPE_CHANG_MUSIC:
int progress = intent.getIntExtra("progress_to", 0);
player.seekTo(progress);
break;
case Config.TYPE_START_MUSIC:
startMusic(intent);
break;
default:
break;
}
return super.onStartCommand(intent, flags, startId);
}
// 播放新音乐
public void startMusic(Intent intent) {
String path = intent.getStringExtra("music_path");// 1.获得要播放的音乐路径
Log.d("music_path", path);
if (player == null) {
player = new MediaPlayer();// 2.避免每次点击建立新对象,会出现叠放音乐的错误
}
player.reset();
try {
player.setDataSource(path);// 设置资源
player.prepare();
player.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer arg0) {
// TODO Auto-generated method stub
player.start();
// 获得音乐总时长,通过广播返回活动中
int duration = player.getDuration();
Log.d("Duration_service", duration+"");
Intent intent = new Intent("music_broadcast_code_action");
intent.putExtra("type", TYPE_MUSIC_DURATION);
intent.putExtra("duration", duration);
sendBroadcast(intent);// 不要忘了动态注册广播
Thread thread = new MyThread();
thread.start();
}
});
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 创建一个线程,返回当前时间
class MyThread extends Thread {// 在子线程中返回当前时间,需要每隔1秒发送//一定要写在循环里
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
while (player.isPlaying()) {
int current = player.getCurrentPosition();
Intent intent = new Intent("music_broadcast_code_action");
intent.putExtra("type", TYPE_MUSIC_CURRENT);
intent.putExtra("current", current);
sendBroadcast(intent);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
ListView适配器:
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
public class MusicAdapter extends BaseAdapter {
private LayoutInflater inflater;
private File[] musics;
private MediaMetadataRetriever mmr ;
public MusicAdapter(LayoutInflater inflater, File[] musics) {
this.inflater = inflater;
this.musics = musics;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return musics.length;
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
@Override
public View getView(int position, View convertView, ViewGroup arg2) {
// TODO Auto-generated method stub
ViewHolder vh = null;
if (convertView == null) {
vh = new ViewHolder();
convertView = inflater.inflate(R.layout.music_item_list, null);
vh.textviewName = (TextView) convertView.findViewById(R.id.textview_music_name);
vh.imgPic = (ImageView) convertView.findViewById(R.id.img_pic);
vh.textviewArtist = (TextView) convertView.findViewById(R.id.textview_artist);
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView.getTag();
}
// 放资源
vh.textviewName.setText(musics[position].getName());
// 放音乐图片和艺术家
mmr = new MediaMetadataRetriever();
mmr.setDataSource(musics[position].getAbsolutePath());
String author = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
if (author != null) {
vh.textviewArtist.setText("艺术家:" + author);
} else {
vh.textviewArtist.setText("艺术家:未知");
}
// 放图片
byte[] imag = mmr.getEmbeddedPicture();// 得到图片
if (imag != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(imag, 0, imag.length);
vh.imgPic.setImageBitmap(bitmap);
} else {
vh.imgPic.setImageResource(R.drawable.den);
}
return convertView;
}
class ViewHolder {
ImageView imgPic;
TextView textviewName;
TextView textviewArtist;
}
}
listview的item布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<ImageView
android:id="@+id/img_pic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal"
android:src="@drawable/ic_launcher" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
<TextView
android:id="@+id/textview_music_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical|center_horizontal"
android:text="音乐名"
android:layout_margin="10dp" />
<TextView
android:id="@+id/textview_artist"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical|center_horizontal"
android:text="艺术家"
android:layout_marginBottom="10dp"
/>
</LinearLayout>
</LinearLayout>
改进:
在效果图中我们看到只有第一个显示出了图片,但其实在手机中是正常显示的,
后期,我增加了功能,加上了暂停,前一首,后一首的功能,还不错呦,只是需要注意暂停后,会使之前控制刷新seekbar的子线程关掉,这里我们需要在建立一个子线程去刷新seekbar。
这里仅进行效果演示: