一.作业目标
实现简单的音乐播放以及切歌功能(只包含一个主界面与一个底部控制台用于切换歌曲)
二.实现步骤
我们实现音乐播放器的流程分为四步:
1.绘制音乐播放器的布局,其中包括主界面音乐列表与列表中每个item的绘制
2.音乐播放器的音频文件获取,这部分要实现对本地音频的获取与传送,并将其展现出来
3.实现在点击RecylceView中item能播放和切换歌曲功能
4.对底部控件进行设置,关联相关操作,以此实现播放暂停音乐,跳转上一曲,下一曲功能
将四个部分完成,即可实现简易的音乐播放器功。
三.关键代码解析以及技术说明
在整个音乐播放器的制作过程中,我们需要首先需要利用到RecycleView在主界面将我们的歌曲列表视图展现出来,这部分,我们还需要设置一个适配器,这里我们采用RecycleView中的Adapter类来完成,将其命名为LocalMusicAdapter。然后需要利用到CardView将每一首歌曲信息以列表元素的形式展现出来,我们在此部分需要对我们想要展现的数据进行传递,还需要对布局进行规划,将其展现在item的合适位置,LocalMusicBean类用于封装每首音乐的信息,并且在加载本地音乐数据时创建LocalMusicBean对象,将其添加到mDatas列表中。。在底部部分,则需要制作一个可以控制音乐播放,暂停,上一曲,下一曲的控制台,这部分需要在对应位置插入图片,并对Button的功能进行监听器设置。至于音乐文件,我们采取读取本地内存中的音乐,利用ContenResolver实现对本地音频内容的访问并接受获取到的音乐文件,然后将其展现在RecycleView中,。最后是音乐的播放部分,我们使用MeidaPlayer来完成
本地音频获取:
SD卡访问权限设置
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
xml设计:
歌曲信息xml设计
提前准备的控制button:
接下来我们着手编写程序主体部分
LocalMusciAdapter
@Override
public void onBindViewHolder(@NonNull LocalMusicViewHolder holder, final int position) {
LocalMusicBean musicBean = mDatas.get(position);
holder.idTv.setText(musicBean.getId());
holder.songTv.setText(musicBean.getSong());
holder.singerTv.setText(musicBean.getSinger());
holder.albumTv.setText(musicBean.getAlbum());
holder.timeTv.setText(musicBean.getDuration());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int currentPosition = holder.getAdapterPosition();
onItemClickListener.OnItemClick(v, currentPosition);
}
});
}
在onBindViewHolder方法中,我们实现的功能是将音乐数据和对应的视图内容进行绑定,并为每个列表项设置点击监听器,以便响应用户的点击操作将数据项与视图进行绑定,即将本地音乐列表中每个音乐的数据填充到对应的文本视图中。首先获取当前位置position的音乐数据项,然后将其对应的各个属性设置到LocalMusicViewHolder实例的相应成员变量中,即设置显示在列表项中的文本内容。接着,通过设置holder.itemView的点击监听器,实现了当用户点击某个本地音乐列表项时的响应操作。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mediaPlayer = new MediaPlayer();
mDatas = new ArrayList<>();
// 创建适配器对象
adapter = new LocalMusicAdapter(this, mDatas);
musicRv.setAdapter(adapter);
// 设置布局管理器
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
musicRv.setLayoutManager(layoutManager);
// 加载本地数据源
loadLocalMusicData();
// 设置每一项的点击事件
setEventListener();
}
在Mainactivity中初始化主活动的布局和视图,创建适配器并与RecyclerView关联,设置布局管理器,加载本地音乐数据,并为每一项设置点击事件。
private void loadLocalMusicData() {
/* 加载本地存储当中的音乐mp3文件到集合当中*/
ContentResolver resolver = getContentResolver();
// 2.获取本地音乐存储的Uri地址
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = resolver.query(uri, null, null, null, null);
// 4.遍历Cursor
int id = 0;
while (cursor.moveToNext()) {
int titleIndex = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
int artistIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
int albumIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
int dataIndex = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);
int durationIndex = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
int albumIdIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID);
if (titleIndex >= 0 && artistIndex >= 0 && albumIndex >= 0 && dataIndex >= 0 && durationIndex >= 0 && albumIdIndex >= 0) {
String song = cursor.getString(titleIndex);
String singer = cursor.getString(artistIndex);
String album = cursor.getString(albumIndex);
id++;
String sid = String.valueOf(id);
String path = cursor.getString(dataIndex);
long duration = cursor.getLong(durationIndex);
SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
String time = sdf.format(new Date(duration));
String album_id = cursor.getString(albumIdIndex);
String albumArt = getAlbumArt(album_id);
LocalMusicBean bean = new LocalMusicBean(sid, song, singer, album, time, path,albumArt);
mDatas.add(bean);
} else {
// 处理列不存在的情况
}
}
// 数据源变化,提示适配器更新
adapter.notifyDataSetChanged();
}
加载本地音乐的方法loadLocalMusicdata
利用ContentResolver对象获取本地音乐存储uri地址,然后使用query()方法查询Uri所代表的数据,返回一个Cursor对象用于遍历查询结果,获取其中的信息,并封装到LocalMusicBean对象中。
此时,我们已经完成了对内存音频文件的获取与展示,接下来就是音频播放部分:
在Adapterd的onBindView中给ViewHolder的整个itemView设置点击事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int currentPosition = holder.getAdapterPosition();
onItemClickListener.OnItemClick(v, currentPosition);
}
});
为RecyclerView的每个item设置点击事件,并在点击时更新当前播放位置,并根据点击位置播放对应的音乐
private void setEventListener() {
/* 设置每一项的点击事件*/
adapter.setOnItemClickListener(new LocalMusicAdapter.OnItemClickListener() {
@Override
public void OnItemClick(View view, int position) {
currnetPlayPosition = position;
LocalMusicBean musicBean = mDatas.get(position);
playMusicInMusicBean(musicBean);
}
});
}
接下来是播放与暂停音乐的函数playMusic
mediaPlayer!=null&&!mediaPlayer.isPlaying()用来确定MediaPlayer对象不为空且当前没有正在播放的音乐
然后,在判断currentPausePositionInSong是否为0的条件下进行不同的操作
public void playMusicInMusicBean(LocalMusicBean musicBean) {
/*根据传入对象播放音乐*/
//设置底部显示的歌手名称和歌曲名
singerTv.setText(musicBean.getSinger());
songTv.setText(musicBean.getSong());
stopMusic();
// 重置多媒体播放器
mediaPlayer.reset();
// 设置新的播放路径
try {
mediaPlayer.setDataSource(musicBean.getPath());
String albumArt = musicBean.getAlbumArt();
Log.i("lsh123", "playMusicInMusicBean: albumpath=="+albumArt);
Bitmap bm = BitmapFactory.decodeFile(albumArt);
Log.i("lsh123", "playMusicInMusicBean: bm=="+bm);
albumIv.setImageBitmap(bm);
playMusic();
} catch (IOException e) {
e.printStackTrace();
}
}
停止音乐函数:
private void stopMusic() {
/* 停止音乐的函数*/
if (mediaPlayer!=null) {
currentPausePositionInSong = 0;
mediaPlayer.pause();
mediaPlayer.seekTo(0);
mediaPlayer.stop();
playIv.setImageResource(R.mipmap.icon_play);
}
}
到此,点击item即可播放音乐,接下来我们要实现的是底部按钮功能,实现对点击事件的监听,根据按钮实现播放,暂停,上一曲,下一曲功能。
暂停:
private void pauseMusic() {
/* 暂停音乐的函数*/
if (mediaPlayer!=null&&mediaPlayer.isPlaying()) {
currentPausePositionInSong = mediaPlayer.getCurrentPosition();
mediaPlayer.pause();
playIv.setImageResource(R.mipmap.icon_play);
}
}
最后一部分控制上一曲与下一曲的功能代码,通过将播放位置进行加一减一操作,进而控制歌曲播放位置,分别对三个button进行了不同功能调用,从而实现了切歌功能。
public void onClick(View v) {
int viewId = v.getId();
if (viewId == R.id.local_music_bottom_iv_last) {
if (currnetPlayPosition == 0) {
Toast.makeText(this, "已经是第一首了,没有上一曲!", Toast.LENGTH_SHORT).show();
return;
}
currnetPlayPosition = currnetPlayPosition - 1;
LocalMusicBean lastBean = mDatas.get(currnetPlayPosition);
playMusicInMusicBean(lastBean);
} else if (viewId == R.id.local_music_bottom_iv_next) {
if (currnetPlayPosition == mDatas.size() - 1) {
Toast.makeText(this, "已经是最后一首了,没有下一曲!", Toast.LENGTH_SHORT).show();
return;
}
currnetPlayPosition = currnetPlayPosition + 1;
LocalMusicBean nextBean = mDatas.get(currnetPlayPosition);
playMusicInMusicBean(nextBean);
} else if (viewId == R.id.local_music_bottom_iv_play) {
if (currnetPlayPosition == -1) {
// 并没有选中要播放的音乐
Toast.makeText(this, "请选择想要播放的音乐", Toast.LENGTH_SHORT).show();
return;
}
if (mediaPlayer.isPlaying()) {
// 此时处于播放状态,需要暂停音乐
pauseMusic();
} else {
// 此时没有播放音乐,点击开始播放音乐
playMusic();
}
}
}
四.效果展示
点击上一曲与下一曲,功能也能正常运作
五.总结
在此次的项目中,实现了基本的歌曲播放与切换功能,但实用性还是较差,并且在播放音乐时,能明显感觉到与专业音乐播放器音质也有所区别,另一方面是底部button会遮挡住左侧展示的歌曲名目信息,在将来的as学习中,我会把这个音乐播放器不断精进,使其功能更加完备。
源码仓库:start/简易音乐播放器