目录
小项目开发——Android 音乐播放器
一、题目
◼ 音乐播放器.
◼ 要求:Activity
编程、ListView
编程、SeekBar
编程、ExoPlayer
编程(播放、暂停、停止、上一首、下一首),音乐文件放在assets/music
目录下,界面自拟.
◼ 期望最终效果:
二、实际最终效果
◼ 分别对应activity_music_list.xml
、activity_my_music_player.xml
的视图.
◼ 点击列表任何一个元素都可以直接跳转到音乐播放界面.
三、模块分析
◼ 从题目所期望的效果来看,需要实现的主要分为3大模块:音乐列表、进度条、功能按钮.
◼ 还有2大可自定义模块:状态栏、导航栏.
但为了尽可能实现像市面上的大部分音乐播放器的界面,我在不改变题目原有主功能的基础上进行了重新设计,即设计了2个 Activity(
MusicListActivity.java
、MyMusicPlayerActivity.java
)及其对应 Layout(activity_music_list.xml
、activity_my_music_player.xml
),分别控制 音乐列表、音乐播放,而不是将它们写在一起.
四、思维导图
◼ 基于以上分析我决定分以下4大模块来进行编程:状态栏、导航栏、音乐列表、音乐播放.
◼ 并以 Layout、Activity 两大块 进行阐述.
五、Layout
1. 自定义 Theme
◼ Path:res/values/themes.xml,添加自定义主题样式,
<style name="Theme.MyMusic" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">#B22196F3</item>
<item name="colorPrimaryVariant">#8B19ADD2</item>
<item name="colorOnPrimary">#FFFFFF</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">#B91976D2</item>
</style>
◼ 并应用在了音乐列表和音乐播放的 Activity 中(即状态栏、导航栏)——AndroidManifest.xml.
android:theme="@style/Theme.MyMusic"
2. 导航栏 LOGO
◼ music_list.xml:将 LOGO 资源图片(icon_music_list.png)修改为 白色 以适配导航栏的背景色.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:src="@drawable/icon_music_list" android:tint="@color/white" />
</item>
</layer-list>
注:LOGO 图标 见 ———> [Music Player/icon_music_list.png · Re.Gin/CSDN - 码云 - 开源中国 (gitee.com)](https://gitee.com/ReGinWZY/csdn/blob/master/Music Player/icon_music_list.png)
3. 音乐列表布局
◼ 文件名:activity_music_list.xml
◼ 采用 约束布局(ConstrainLayout
)
◼ 布局id
:cl_music_list
◼ 只有一个 空白的 占满整个屏幕的ListView
组件:
4. 音乐播放布局
◼ 文件名:activity_my_music_player.xml
◼ 采用 约束布局(ConstrainLayout
)
◼ 布局id
:cl_music_player
注:剩下没贴图的
ImageButton
同右下角的ibtn_play
,只是图标、位置、放缩倍率不同.音乐封面可去网易云官网复制.
按钮图标 见 ———> [Music Player/播放按钮图标 · Re.Gin/CSDN - 码云 - 开源中国 (gitee.com)](https://gitee.com/ReGinWZY/csdn/tree/master/Music Player/播放按钮图标)
5. 设置APP图标及名字
android:icon="@drawable/headphone"
android:label="MyMusicPlayer"
注:APP 图标见 ———> [Music Player/headphone.png · Re.Gin/CSDN - 码云 - 开源中国 (gitee.com)](https://gitee.com/ReGinWZY/csdn/blob/master/Music Player/headphone.png)
六、Activity
1. 音乐列表 Activity
◼ 文件名:MusicListActivity.java
⑴ 列表元素点击监听器
AdapterView.OnItemClickListener mListenerLv = (parent, view, position, id) -> {
mIntent.putExtra("selectedIndex", position); // 传选中音乐下标
startActivity(mIntent); // 跳转至 MyMusicPlayerActivity
}; // end mLvListener
⑵ 获取音乐名
public void getMusics() {
try {
String[] musicFileNames = getAssets().list("musics");
for (int i = 0; i < musicFileNames.length; ++i) {
musicFileNames[i] = musicFileNames[i].split("\\.")[0]; // 以“.”分割字符串得到不含后缀的音乐名
mMusicList.add(musicFileNames[i]);
} // end for
mIntent.putExtra("musicArray", musicFileNames); // 将整个音乐列表传给 MyMusicPlayerActivity
} catch (IOException e) {
throw new RuntimeException(e);
} // end catch
} // end getMusics
2. 音乐播放 Activity
◼ 文件名:MyMusicPlayerActivity.java
⑴ 获取音乐列表信息
public void getIntentMsg() {
sMusicIndex = mIntent.getIntExtra("selectedIndex", 0); // 默认第一首音乐
sMusicArray = mIntent.getStringArrayExtra("musicArray");
} // end getIntentMsg
⑵ 音乐封面圆形剪裁和旋转动画
API | 说明 |
---|---|
circularCutting |
给Bitmap 对象进行圆形剪裁 |
addAnimation |
给ImageView 对象添加动画 |
public Bitmap circularCutting(Bitmap bitmap) {
mBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mBitmap);
Paint paint = new Paint();
paint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
canvas.drawCircle(bitmap.getWidth() / 2f, bitmap.getHeight() / 2f, bitmap.getWidth() / 2f, paint);
return mBitmap;
} // end circularCutting
◼ 首先,使用
Bitmap.createBitmap()
以ARGB_8888
一种32位颜色深度的色彩模式创建一个与原始bitmap
大小相同且高质量的位图对象mBitmap
.◼ 然后,先使用
Canvas
对象将该位图对象绘制成一个圆形,再使用Paint
对象的setShader()
方法设置圆形填充色,通过BitmapShader
对象将原始bitmap
作为填充纹理.◼ 最后,将处理后的位图对象返回.
public void addAnimation(ImageView iv) {
mAnimator = ObjectAnimator.ofFloat(iv, "rotation", 0f, 360f); // 360°旋转
mAnimator.setDuration(40000); // 毫秒
mAnimator.setRepeatCount(ObjectAnimator.INFINITE); // 动画无限循环
mAnimator.setInterpolator(new LinearInterpolator()); // 线性插值器
mAnimator.start(); // 启动动画
} // end addAnimation
◼ 首先使用
ObjectAnimator.ofFloat()
方法创建一个ObjectAnimator
对象,将其绑定到ImageView
对象的rotation
属性上,设置动画的起始值和结束值,以及动画的持续时间和重复次数.◼ 然后,使用
setInterpolator()
方法设置动画插值器为线性插值器.◼ 最后调用
start()
方法启动动画.
⑶ 设置音乐播放相关资源
API | 说明 |
---|---|
mPlayerListener |
音乐播放器监听器 |
initExoPlayer |
初始化音乐播放器(ExoPlayer 对象) |
updateMusicPlayer |
更新音乐播放器 |
updateMusicLayout |
更新音乐播放器页面 |
Player.Listener mPlayerListener = new Player.Listener() {
@Override
public void onPlaybackStateChanged(int playbackState) {
if (playbackState == ExoPlayer.STATE_READY) { // 播放器准备好了
mExoPlayer.play();
mPlayStateIbtn.setImageResource(R.drawable.pause);
mTimer.schedule(new ProgressUpdate(), 0, 500);
} // end if
} // end onPlaybackStateChanged
}; // end mPlayerListener
public void initExoPlayer() {
mExoPlayer = new ExoPlayer.Builder(MyMusicPlayerActivity.this).build();
/* 一次性将所有音乐资源添加到音乐播放器中 */
for (String musicName : sMusicArray) {
mediaItem = MediaItem.fromUri("asset:///musics/" + musicName + ".mp3");
mExoPlayer.addMediaItem(mediaItem);
} // end for
updateMusicPlayer(sMusicIndex);
mExoPlayer.setRepeatMode(ExoPlayer.REPEAT_MODE_ALL); // 默认列表循环
mPlayMode = mExoPlayer.getRepeatMode();
mExoPlayer.addListener(mPlayerListener);
} // end initExoPlayer
◼ 对于 asset 文件夹里的资源,可以以asset:///path形式得到资源的
URI
.文章来源:https://www.toymoban.com/news/detail-767903.html◼ 这里一次性将 MusicListActivity 传来的所有音乐资源添加到音乐播放器中,以便后续直接通过 索引位置 进行相关操作.文章来源地址https://www.toymoban.com/news/detail-767903.html
public void updateMusicPlayer(int index) {
updateMusicLayout(index); // 更新页面
mExoPlayer.seekTo(index, 0);
mExoPlayer.prepare();
} // end updateExoPlayer
public void updateMusicLayout(int index) {
mMusicTitleTv.setText(sMusicArray[index]);
try {
InputStream inputStream = getAssets().open("music_images/" + sMusicArray[index] + ".jpg");
mBitmap = BitmapFactory.decodeStream(inputStream);
mMusicCoverIv.setImageBitmap(circularCutting(mBitmap));
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
} // end catch
} // end updateMusicLayout
⑷ 进度条
SeekBar.OnSeekBarChangeListener mListenerSb = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
/* 从用户拖动到的位置开始播放 */
if (fromUser) {
mExoPlayer.seekTo(sMusicIndex, progress);
}
/* 列表循环状态下,音乐会自动到下一首,此时需要重新渲染页面元素 */
if (sMusicIndex != mExoPlayer.getCurrentMediaItemIndex()
&& mPlayMode == mExoPlayer.REPEAT_MODE_ALL) {
sMusicIndex = mExoPlayer.getCurrentMediaItemIndex();
updateMusicLayout(sMusicIndex);
mAnimator.cancel();
mAnimator.start();
} // end if
} // end onProgressChanged
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
} // end onStartTrackingTouch
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mAnimator.resume(); // 用户停止拖动进度条,图片重新旋转
mStartTimeTv.setText(formatPosition(seekBar.getProgress()));
} // end onStopTrackingTouch
}; // end mListenerSb
/**
* 内部类——定时任务类:定时更新 SeekBar 进度条
*/
private class ProgressUpdate extends TimerTask {
@Override
public void run() {
runOnUiThread(() -> {
mStartPos = mExoPlayer.getContentPosition();
mMusicProgressSb.setProgress((int) mStartPos);
mStartTimeTv.setText(formatPosition(mStartPos));
mDurationPos = mExoPlayer.getDuration();
mMusicProgressSb.setMax((int) mDurationPos);
mDurationTimeTv.setText(formatPosition(mDurationPos));
}); // end runOnUiThread
} // end run
} // end ProgressUpdate
/**
* 格式化音乐进度条起始、终止位置,显示“分:秒”
*
* @param pos 音乐进度条位置
* @return “分:秒”
*/
public String formatPosition(long pos) {
@SuppressLint("SimpleDateFormat")
SimpleDateFormat sdf = new SimpleDateFormat("mm:ss"); // "分:秒"格式
return sdf.format(pos);
} // end format
⑸ 功能按钮
API | 说明 |
---|---|
changePlayerMode |
更换音乐播放模式 |
previousMusic |
上一首 |
changePlayerState |
播放/暂停 |
nextMusic |
下一首 |
stopMusic |
停止音乐 |
public void changePlayerMode() {
if (mPlayMode == mExoPlayer.REPEAT_MODE_ALL) {
mPlayModeIbtn.setImageResource(R.drawable.repeat_once);
mExoPlayer.setRepeatMode(ExoPlayer.REPEAT_MODE_ONE);
mPlayMode = ExoPlayer.REPEAT_MODE_ONE;
Toast.makeText(MyMusicPlayerActivity.this, "单曲循环", Toast.LENGTH_SHORT).show();
} else {
mPlayModeIbtn.setImageResource(R.drawable.repeat_all);
mExoPlayer.setRepeatMode(ExoPlayer.REPEAT_MODE_ALL);
mPlayMode = ExoPlayer.REPEAT_MODE_ALL;
Toast.makeText(MyMusicPlayerActivity.this, "列表循环", Toast.LENGTH_SHORT).show();
} // end else
} // end changePlayerMode
public void previousMusic() {
if (sMusicIndex == 0) {
sMusicIndex = sMusicArray.length - 1;
} else sMusicIndex--;
mAnimator.cancel(); // 上一首取消动画
mAnimator.start(); // 并重新开始旋转
updateMusicPlayer(sMusicIndex);
} // end previousMusic
public void changePlayerState() {
if (mExoPlayer.isPlaying()) {
mExoPlayer.pause();
mAnimator.pause(); // 暂停动画,直到遇上 resume()
mPlayStateIbtn.setImageResource(R.drawable.play);
mTimer.cancel();
mTimer = new Timer();
Toast.makeText(MyMusicPlayerActivity.this, "暂停", Toast.LENGTH_SHORT).show();
} else {
mExoPlayer.play();
mAnimator.resume(); // 将暂停的动画重新从当前位置开始旋转,而不是重新开始
mPlayStateIbtn.setImageResource(R.drawable.pause);
mTimer = new Timer();
mTimer.schedule(new ProgressUpdate(), 0, 500);
Toast.makeText(MyMusicPlayerActivity.this, "播放", Toast.LENGTH_SHORT).show();
} // end else
} // end changePlayerState
public void nextMusic() {
if (sMusicIndex == sMusicArray.length - 1) {
sMusicIndex = 0;
} else sMusicIndex++;
mAnimator.cancel(); // 下一首取消动画
mAnimator.start(); // 重新开始旋转
updateMusicPlayer(sMusicIndex);
} // end nextMusic
public void stopMusic() {
finish();
} // end stopMusic
到了这里,关于Android 音乐播放器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!