目錄
小項(xiàng)目開(kāi)發(fā)——Android 音樂(lè)播放器
一、題目
? 音樂(lè)播放器.
? 要求:Activity
編程、ListView
編程、SeekBar
編程、ExoPlayer
編程(播放、暫停、停止、上一首、下一首),音樂(lè)文件放在assets/music
目錄下,界面自擬.
? 期望最終效果:
二、實(shí)際最終效果
? 分別對(duì)應(yīng)activity_music_list.xml
、activity_my_music_player.xml
的視圖.
? 點(diǎn)擊列表任何一個(gè)元素都可以直接跳轉(zhuǎn)到音樂(lè)播放界面.
三、模塊分析
? 從題目所期望的效果來(lái)看,需要實(shí)現(xiàn)的主要分為3大模塊:音樂(lè)列表、進(jìn)度條、功能按鈕.
? 還有2大可自定義模塊:狀態(tài)欄、導(dǎo)航欄.
但為了盡可能實(shí)現(xiàn)像市面上的大部分音樂(lè)播放器的界面,我在不改變題目原有主功能的基礎(chǔ)上進(jìn)行了重新設(shè)計(jì),即設(shè)計(jì)了2個(gè) Activity(
MusicListActivity.java
、MyMusicPlayerActivity.java
)及其對(duì)應(yīng) Layout(activity_music_list.xml
、activity_my_music_player.xml
),分別控制 音樂(lè)列表、音樂(lè)播放,而不是將它們寫(xiě)在一起.
四、思維導(dǎo)圖
? 基于以上分析我決定分以下4大模塊來(lái)進(jìn)行編程:狀態(tài)欄、導(dǎo)航欄、音樂(lè)列表、音樂(lè)播放.
? 并以 Layout、Activity 兩大塊 進(jìn)行闡述.
五、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>
? 并應(yīng)用在了音樂(lè)列表和音樂(lè)播放的 Activity 中(即狀態(tài)欄、導(dǎo)航欄)——AndroidManifest.xml.
android:theme="@style/Theme.MyMusic"
2. 導(dǎo)航欄 LOGO
? music_list.xml:將 LOGO 資源圖片(icon_music_list.png)修改為 白色 以適配導(dǎo)航欄的背景色.
<?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 圖標(biāo) 見(jiàn) ———> [Music Player/icon_music_list.png · Re.Gin/CSDN - 碼云 - 開(kāi)源中國(guó) (gitee.com)](https://gitee.com/ReGinWZY/csdn/blob/master/Music Player/icon_music_list.png)
3. 音樂(lè)列表布局
? 文件名:activity_music_list.xml
? 采用 約束布局(ConstrainLayout
)
? 布局id
:cl_music_list
? 只有一個(gè) 空白的 占滿整個(gè)屏幕的ListView
組件:
4. 音樂(lè)播放布局
? 文件名:activity_my_music_player.xml
? 采用 約束布局(ConstrainLayout
)
? 布局id
:cl_music_player
注:剩下沒(méi)貼圖的
ImageButton
同右下角的ibtn_play
,只是圖標(biāo)、位置、放縮倍率不同.音樂(lè)封面可去網(wǎng)易云官網(wǎng)復(fù)制.
按鈕圖標(biāo) 見(jiàn) ———> [Music Player/播放按鈕圖標(biāo) · Re.Gin/CSDN - 碼云 - 開(kāi)源中國(guó) (gitee.com)](https://gitee.com/ReGinWZY/csdn/tree/master/Music Player/播放按鈕圖標(biāo))
5. 設(shè)置APP圖標(biāo)及名字
android:icon="@drawable/headphone"
android:label="MyMusicPlayer"
注:APP 圖標(biāo)見(jiàn) ———> [Music Player/headphone.png · Re.Gin/CSDN - 碼云 - 開(kāi)源中國(guó) (gitee.com)](https://gitee.com/ReGinWZY/csdn/blob/master/Music Player/headphone.png)
六、Activity
1. 音樂(lè)列表 Activity
? 文件名:MusicListActivity.java
⑴ 列表元素點(diǎn)擊監(jiān)聽(tīng)器
AdapterView.OnItemClickListener mListenerLv = (parent, view, position, id) -> {
mIntent.putExtra("selectedIndex", position); // 傳選中音樂(lè)下標(biāo)
startActivity(mIntent); // 跳轉(zhuǎn)至 MyMusicPlayerActivity
}; // end mLvListener
⑵ 獲取音樂(lè)名
public void getMusics() {
try {
String[] musicFileNames = getAssets().list("musics");
for (int i = 0; i < musicFileNames.length; ++i) {
musicFileNames[i] = musicFileNames[i].split("\\.")[0]; // 以“.”分割字符串得到不含后綴的音樂(lè)名
mMusicList.add(musicFileNames[i]);
} // end for
mIntent.putExtra("musicArray", musicFileNames); // 將整個(gè)音樂(lè)列表傳給 MyMusicPlayerActivity
} catch (IOException e) {
throw new RuntimeException(e);
} // end catch
} // end getMusics
2. 音樂(lè)播放 Activity
? 文件名:MyMusicPlayerActivity.java
⑴ 獲取音樂(lè)列表信息
public void getIntentMsg() {
sMusicIndex = mIntent.getIntExtra("selectedIndex", 0); // 默認(rèn)第一首音樂(lè)
sMusicArray = mIntent.getStringArrayExtra("musicArray");
} // end getIntentMsg
⑵ 音樂(lè)封面圓形剪裁和旋轉(zhuǎn)動(dòng)畫(huà)
API | 說(shuō)明 |
---|---|
circularCutting |
給Bitmap 對(duì)象進(jìn)行圓形剪裁 |
addAnimation |
給ImageView 對(duì)象添加動(dòng)畫(huà) |
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位顏色深度的色彩模式創(chuàng)建一個(gè)與原始bitmap
大小相同且高質(zhì)量的位圖對(duì)象mBitmap
.? 然后,先使用
Canvas
對(duì)象將該位圖對(duì)象繪制成一個(gè)圓形,再使用Paint
對(duì)象的setShader()
方法設(shè)置圓形填充色,通過(guò)BitmapShader
對(duì)象將原始bitmap
作為填充紋理.? 最后,將處理后的位圖對(duì)象返回.
public void addAnimation(ImageView iv) {
mAnimator = ObjectAnimator.ofFloat(iv, "rotation", 0f, 360f); // 360°旋轉(zhuǎn)
mAnimator.setDuration(40000); // 毫秒
mAnimator.setRepeatCount(ObjectAnimator.INFINITE); // 動(dòng)畫(huà)無(wú)限循環(huán)
mAnimator.setInterpolator(new LinearInterpolator()); // 線性插值器
mAnimator.start(); // 啟動(dòng)動(dòng)畫(huà)
} // end addAnimation
? 首先使用
ObjectAnimator.ofFloat()
方法創(chuàng)建一個(gè)ObjectAnimator
對(duì)象,將其綁定到ImageView
對(duì)象的rotation
屬性上,設(shè)置動(dòng)畫(huà)的起始值和結(jié)束值,以及動(dòng)畫(huà)的持續(xù)時(shí)間和重復(fù)次數(shù).? 然后,使用
setInterpolator()
方法設(shè)置動(dòng)畫(huà)插值器為線性插值器.? 最后調(diào)用
start()
方法啟動(dòng)動(dòng)畫(huà).
⑶ 設(shè)置音樂(lè)播放相關(guān)資源
API | 說(shuō)明 |
---|---|
mPlayerListener |
音樂(lè)播放器監(jiān)聽(tīng)器 |
initExoPlayer |
初始化音樂(lè)播放器(ExoPlayer 對(duì)象) |
updateMusicPlayer |
更新音樂(lè)播放器 |
updateMusicLayout |
更新音樂(lè)播放器頁(yè)面 |
Player.Listener mPlayerListener = new Player.Listener() {
@Override
public void onPlaybackStateChanged(int playbackState) {
if (playbackState == ExoPlayer.STATE_READY) { // 播放器準(zhǔn)備好了
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();
/* 一次性將所有音樂(lè)資源添加到音樂(lè)播放器中 */
for (String musicName : sMusicArray) {
mediaItem = MediaItem.fromUri("asset:///musics/" + musicName + ".mp3");
mExoPlayer.addMediaItem(mediaItem);
} // end for
updateMusicPlayer(sMusicIndex);
mExoPlayer.setRepeatMode(ExoPlayer.REPEAT_MODE_ALL); // 默認(rèn)列表循環(huán)
mPlayMode = mExoPlayer.getRepeatMode();
mExoPlayer.addListener(mPlayerListener);
} // end initExoPlayer
? 對(duì)于 asset 文件夾里的資源,可以以asset:///path形式得到資源的
URI
.文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-813244.html? 這里一次性將 MusicListActivity 傳來(lái)的所有音樂(lè)資源添加到音樂(lè)播放器中,以便后續(xù)直接通過(guò) 索引位置 進(jìn)行相關(guān)操作.文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-813244.html
public void updateMusicPlayer(int index) {
updateMusicLayout(index); // 更新頁(yè)面
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
⑷ 進(jìn)度條
SeekBar.OnSeekBarChangeListener mListenerSb = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
/* 從用戶拖動(dòng)到的位置開(kāi)始播放 */
if (fromUser) {
mExoPlayer.seekTo(sMusicIndex, progress);
}
/* 列表循環(huán)狀態(tài)下,音樂(lè)會(huì)自動(dòng)到下一首,此時(shí)需要重新渲染頁(yè)面元素 */
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(); // 用戶停止拖動(dòng)進(jìn)度條,圖片重新旋轉(zhuǎn)
mStartTimeTv.setText(formatPosition(seekBar.getProgress()));
} // end onStopTrackingTouch
}; // end mListenerSb
/**
* 內(nèi)部類(lèi)——定時(shí)任務(wù)類(lèi):定時(shí)更新 SeekBar 進(jìn)度條
*/
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
/**
* 格式化音樂(lè)進(jìn)度條起始、終止位置,顯示“分:秒”
*
* @param pos 音樂(lè)進(jìn)度條位置
* @return “分:秒”
*/
public String formatPosition(long pos) {
@SuppressLint("SimpleDateFormat")
SimpleDateFormat sdf = new SimpleDateFormat("mm:ss"); // "分:秒"格式
return sdf.format(pos);
} // end format
⑸ 功能按鈕
API | 說(shuō)明 |
---|---|
changePlayerMode |
更換音樂(lè)播放模式 |
previousMusic |
上一首 |
changePlayerState |
播放/暫停 |
nextMusic |
下一首 |
stopMusic |
停止音樂(lè) |
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, "單曲循環(huán)", 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, "列表循環(huán)", Toast.LENGTH_SHORT).show();
} // end else
} // end changePlayerMode
public void previousMusic() {
if (sMusicIndex == 0) {
sMusicIndex = sMusicArray.length - 1;
} else sMusicIndex--;
mAnimator.cancel(); // 上一首取消動(dòng)畫(huà)
mAnimator.start(); // 并重新開(kāi)始旋轉(zhuǎn)
updateMusicPlayer(sMusicIndex);
} // end previousMusic
public void changePlayerState() {
if (mExoPlayer.isPlaying()) {
mExoPlayer.pause();
mAnimator.pause(); // 暫停動(dòng)畫(huà),直到遇上 resume()
mPlayStateIbtn.setImageResource(R.drawable.play);
mTimer.cancel();
mTimer = new Timer();
Toast.makeText(MyMusicPlayerActivity.this, "暫停", Toast.LENGTH_SHORT).show();
} else {
mExoPlayer.play();
mAnimator.resume(); // 將暫停的動(dòng)畫(huà)重新從當(dāng)前位置開(kāi)始旋轉(zhuǎn),而不是重新開(kāi)始
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(); // 下一首取消動(dòng)畫(huà)
mAnimator.start(); // 重新開(kāi)始旋轉(zhuǎn)
updateMusicPlayer(sMusicIndex);
} // end nextMusic
public void stopMusic() {
finish();
} // end stopMusic
到了這里,關(guān)于小項(xiàng)目開(kāi)發(fā)——Android 音樂(lè)播放器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!