這次介紹一個多功能音樂播放器,記得是大二那年寒假寫的,實現的主要功能就是音樂播放,帶進度條控制,掃描本地音樂,上一曲下一曲,播放類型(單曲循環,順序播放,隨機播放),APP主題換膚,背景圖更換等,功能都比較基礎,基本上如果你不會的話,跟着我的思路,應該都是能實現的,預計會在以後加入歌詞的功能。
在開始前,先放一張最後的效果圖吧,我個人喜歡的風格,簡約,美觀。
目錄
1.實現掃描本地音樂
2.音樂的播放與控制
3.關聯進度條seekbar,自定義seekbar
4.單曲循環,順序播放,隨機播放的實現
5.設置喜愛音樂
6.播放列表背景圖設置與保存
7.實現APP主題換膚的功能
正文
1.實現掃描本地音樂
這裏爲了將每個系統裏面存放的音樂抽象出來,也是爲了方便管理,先定義一個音樂類Song,代碼如下
public class Song {
/** * 歌手 */
private String singer;
/** * 歌曲名 */
private String song;
/** * 歌曲的地址 */
private String path;
/** * 歌曲長度 */
private int duration;
/** * 歌曲的大小 */
private long size;
public String getSinger() {
return singer;
}
public void setSinger(String singer) {
this.singer = singer;
}
public String getSong() {
return song;
}
public void setSong(String song) {
this.song = song;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
}
然後我們再寫一個工具類,這個工具類實現的功能就是掃描系統中的本地音樂,返回一個List<Song>集合,供我們使用,代碼如下
public class MusicUtils {
/**
* 掃描系統裏面的音頻文件,返回一個list集合
*/
public static List<Song> getMusicData(Context context) {
List<Song> list = new ArrayList<>();
Cursor cursor = context.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.AudioColumns.IS_MUSIC);
if (cursor != null) {
while (cursor.moveToNext()) {
Song song = new Song();
song.setSong( cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)));
song.setSinger( cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)));
song.setPath(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)));
song.setDuration( cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)));
song.setSize( cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)));
if (song.getSize() > 1000 * 800) {//過濾掉短音頻
// 分離出歌曲名和歌手
if (song.getSong().contains("-")) {
String[] str = song.getSong().split("-");
song.setSinger( str[0]);
song.setSong( str[1]);
}
list.add(song);
}
}
// 釋放資源
cursor.close();
}
return list;
}
//格式化時間
public static String formatTime(int time) {
if (time / 1000 % 60 < 10) {
return time / 1000 / 60 + ":0" + time / 1000 % 60;
} else {
return time / 1000 / 60 + ":" + time / 1000 % 60;
}
}
}
然後,在佈局裏定義一個Listview,再給Listview寫一個適配器,一般繼承自BaseAdapter,adapter代碼如下
public class MyAdapter extends BaseAdapter {
private Context context;
private List<Song> list;
private int position_flag = 0;
public MyAdapter(MainActivity mainActivity, List<Song> list) {
this.context = mainActivity;
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int i) {
return list.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder = null;
if (view == null) {
holder = new ViewHolder();
// 引入佈局
view = View.inflate(context, R.layout.list_item, null);
// 實例化對象
holder.song = (TextView) view.findViewById(R.id.item_mymusic_song);
holder.singer = (TextView) view
.findViewById(R.id.item_mymusic_singer);
holder.duration = (TextView) view
.findViewById(R.id.item_mymusic_duration);
holder.position = (TextView) view
.findViewById(R.id.item_mymusic_postion);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
// 給控件賦值
String string_song = list.get(i).getSong();
if (string_song.length() >= 5
&& string_song.substring(string_song.length() - 4,
string_song.length()).equals(".mp3")) {
holder.song.setText(string_song.substring(0,
string_song.length() - 4).trim());
} else {
holder.song.setText(string_song.trim());
}
holder.singer.setText(list.get(i).getSinger().toString().trim());
// 時間轉換爲時分秒
int duration = list.get(i).getDuration();
String time = MusicUtils.formatTime(duration);
holder.duration.setText(time);
return view;
}
class ViewHolder {
TextView song;// 歌曲名
TextView singer;// 歌手
TextView duration;// 時長
TextView position;// 序號
}
}
adapter裏面的列表項佈局代碼如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:id="@+id/item_mymusic_postion"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignBottom="@+id/item_mymusic_singer"
android:layout_gravity="center_vertical"
android:gravity="center"
android:layout_margin="10dp"
android:text="1"
android:textSize="18sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/item_mymusic_song"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:ellipsize="end"
android:maxLines="1"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:text="歌曲名"
android:textSize="18sp" />
<TextView
android:id="@+id/item_mymusic_singer"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/item_mymusic_duration"
android:gravity="bottom"
android:text="歌手"
android:ellipsize="end"
android:maxLines="1"
android:layout_marginBottom="5dp"
android:textSize="16sp" />
<TextView
android:id="@+id/item_mymusic_duration"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:gravity="bottom"
android:layout_marginRight="5dp"
android:layout_marginBottom="5dp"
android:text="歌曲時間"
android:textSize="16sp" />
</RelativeLayout>
</LinearLayout>
然後在Listview所在的activity裏,調用工具類獲取音樂集合,構造適配器,給Listview設置適配器,即可在Listview中顯示本地所有的音樂啦,關鍵代碼就三行,如下
List<Song> list = MusicUtils.getMusicData(MainActivity.this);
MyAdapter adapter = new MyAdapter(MainActivity.this, list);
listview.setAdapter(adapter);
好了,到現在爲止,你已經實現了,顯示手機裏所有的音樂,但是還不能播放,怎麼播放,接着往下看
2.音樂的播放與控制
實現音樂播放,需要用到的類爲MediaPlayer,爲了方便,封裝一個播放音樂的方法,如下
private void musicplay(int position) {
try {
mplayer.reset();
mplayer.setDataSource(list.get(position).getPath());
mplayer.prepare();
mplayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
傳入的position爲,播放的音樂的位置,即序號。
然後給listview設置點擊事件
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
musicplay(currentposition);
}
});
這樣我們只是實現了簡單的播放,點擊Listview對應的條目,即可播放對應的音樂
我們下一步就是實現,音樂播放的控制,即暫停,下一曲,上一曲的實現
首先是暫停,在播放按鈕的點擊時間中,我們通常的需求是這樣的,如果當前音樂正在播放,那麼點擊,暫停音樂,再點擊,即可再次接着上次的繼續播放,所以在播放按鈕的點擊事件中,需要根據不同情況處理,同時爲了直觀,需要準備兩張圖片,播放的時候一張,暫停的時候一張,播放按鈕的點擊事件如下
imageView_play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mplayer.isPlaying()) {
mplayer.pause();
imageview.clearAnimation();
} else {
mplayer.start();
// thread = new Thread(new SeekBarThread());
// thread.start();
imageview.startAnimation(AnimationUtils.loadAnimation(
MainActivity.this, R.anim.imageview_rotate));
}
}
});
由於爲了界面體驗良好,我這裏還設置了,當音樂播放的時候,左側圖片的旋轉效果,代碼已經在上面的點擊事件中,效果圖如下
左側imageview的動畫代碼如下
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="20000"
android:fromDegrees="0"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="-1"
android:repeatMode="restart"
android:toDegrees="360" />
扯了點其他的,下面來實現上一曲和下一曲的效果,我們也可以和播放一個,分別寫一個對應的方法
上一曲方法代碼如下
// 上一曲
private void frontMusic() {
currentposition--;
if (currentposition < 0) {
currentposition = list.size() - 1;
}
musicplay(currentposition);
}
其中,currentposition是記錄的當前音樂播放序號,這裏有一點需要考慮的是,當前播放音樂的序號爲0的時候,進行--操作之後那麼會變成負數,所以,這裏根據邏輯,處理爲播放列表最後一曲,即設置序號爲list.size()-1,形成一個環形。
相信你看了上一曲的方法,那麼下一曲也很簡單了,下一曲方法代碼如下
// 下一曲
private void nextMusic() {
currentposition++;
if (currentposition > list.size() - 1) {
currentposition = 0;
}
musicplay(currentposition);
}
同樣我們也需要處理播放歌曲到最後一曲的時候,設置爲播放列表第一首歌曲。
3.關聯進度條seekbar,自定義seekbar
關聯進度條的方法也很簡單,這裏將更新seekbar的方法重新開了一個線程,專門處理更新,代碼如下
// 自定義的線程,用於下方seekbar的刷新
class SeekBarThread implements Runnable {
@Override
public void run() {
while (!ischanging && mplayer.isPlaying()) {
// 將SeekBar位置設置到當前播放位置
seekBar.setProgress(mplayer.getCurrentPosition());
try {
// 每500毫秒更新一次位置
Thread.sleep(500);
// 播放進度
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
其中,ischanging用於判斷當前的seekbar是否處於滑動狀態,然後在音樂播放的地方,也就是剛纔封裝的musicplay方法中,更改爲如下代碼
private void musicplay(int position) {
seekBar.setMax(list.get(position).getDuration());
imageview.startAnimation(AnimationUtils.loadAnimation(
MainActivity.this, R.anim.imageview_rotate));
try {
mplayer.reset();
mplayer.setDataSource(list.get(position).getPath());
mplayer.prepare();
mplayer.start();
} catch (Exception e) {
e.printStackTrace();
}
thread = new Thread(new SeekBarThread());
thread.start();
}
當然,不要忘了先設置seekbar的最大刻度值,也就是上面代碼中setMax方法。
至此,你的音樂播放就已經和seekbar進度條關聯起來了,但是你可能會發現系統默認的進度條很醜,不符合你的審美,那麼我們就需要更改seekbar的樣式,也就是自定義seekbar。
自定義seekbar,需要在佈局中設置progressDrawable和thumb,分別對應進度條的背景和進度條的指示小圖標,我這裏進度條的背景採用的是drawable,代碼如下
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/background">
<shape>
<solid
android:color="#DCDCDC"/>
</shape>
</item>
<item android:id="@+id/secondaryProgress">
<clip>
<shape>
<solid android:color="@color/blue" />
</shape>
</clip>
</item>
<item android:id="@+id/progress">
<clip>
<shape>
<solid android:color="@color/blue" />
</shape>
</clip>
</item>
</layer-list>
而thumb,我這裏使用的就是一張圖片,可以在我的項目源代碼中找到,圖片長下面這個樣子
當然你也可以採用自己的圖片,來實現炫酷的效果哦!
4.單曲循環,順序播放,隨機播放的實現
實現這個效果,首先我哦們定義一個變量,用於記錄當前的播放類型是哪種,如下
// 用於判斷當前的播放順序,0->單曲循環,1->順序播放,2->隨機播放
private int play_style = 0;
然後在我們的更改播放類型的按鈕點擊事件中,更改它的值,點擊事件代碼如下
imageview_playstyle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
play_style++;
if (play_style > 2) {
play_style = 0;
}
switch (play_style) {
case 0:
imageview_playstyle.setImageResource(R.mipmap.cicle);
Toast.makeText(MainActivity.this, "單曲循環",
Toast.LENGTH_SHORT).show();
break;
case 1:
imageview_playstyle.setImageResource(R.mipmap.ordered);
Toast.makeText(MainActivity.this, "順序播放",
Toast.LENGTH_SHORT).show();
break;
case 2:
imageview_playstyle.setImageResource(R.mipmap.unordered);
Toast.makeText(MainActivity.this, "隨機播放",
Toast.LENGTH_SHORT).show();
break;
}
}
});
邏輯比較簡單,應該都能看懂,然後就是怎麼根據這個變量來實現對應的效果,核心方法就是MediaPLayer的setOnCompeleteListener,代碼如下
// 監聽mediaplayer播放完畢時調用
mplayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
// TODO Auto-generated method stub
switch (play_style) {
case 0:
musicplay(currentposition);
break;
case 1:
nextMusic();
break;
case 2:
random_nextMusic();
break;
default:
break;
}
}
});
下一曲的代碼上面已經給出了,下面是隨機播放下一曲的代碼,思想很簡單,就是生成一個隨機數,再設置爲currentpositon,然後調用musicplay方法即可
// 隨機播放下一曲
private void random_nextMusic() {
currentposition = currentposition + random.nextInt(list.size() - 1);
currentposition %= list.size();
musicplay(currentposition);
}
5.設置喜愛音樂
喜愛音樂的設置,我這裏處理的比較簡單, 當長按列表項的時候,彈出對話框,用於設置喜愛音樂,效果如下
然後,用sharepreference記錄下喜愛音樂的序號值,當要播放喜愛音樂的時候,直接取到該序號值,然後調用musicplay方法播放序號值對應的音樂即可。主要就是sharepreference的使用,代碼很簡單,就不貼了
6.播放列表背景圖設置與保存
設置播放列表背景也就是調用一下,listview.setBackground即可,但是我們如果不進行保存的話,下次進入APP的時候,背景圖可能又恢復爲初始的,那麼我們就需要保存列表ode背景圖,這裏也採用sharepreference來保存,首先用Base64將圖片轉換爲String,然後保存起來,下次進入APP的時候,再取出來,用Base64將String轉爲drawable對象,在設置上去即可。相關代碼如下。
// 使用sharedPreferences保存listview背景圖片
private void saveDrawable(Drawable drawable) {
SharedPreferences.Editor editor = sharedPreferences.edit();
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
// Bitmap bitmap = BitmapFactory.decodeResource(getResources(), id);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, baos);
String imageBase64 = new String(Base64.encodeToString(
baos.toByteArray(), Base64.DEFAULT));
editor.putString("listbg", imageBase64);
editor.commit();
}
// 加載用sharedPreferences保存的圖片
private Drawable loadDrawable() {
String temp = sharedPreferences.getString("listbg", "");
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(
temp.getBytes(), Base64.DEFAULT));
return Drawable.createFromStream(bais, "");
}
7.實現APP主題換膚的功能
實現主題效果,有很多種方法,我這裏採用的是自定義屬性的方法,首先我們在values下新建一個文件attrs,內容如下
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="theme_color" format="color" />
<attr name="popupwindow_bg" format="reference"/>
<attr name="dialogactivity_bg" format="reference"/>
<attr name="btn_submit_bg" format="reference"/>
<attr name="seekbar_progress_bg" format="reference"/>
<attr name="play_image" format="reference" />
<attr name="next_image" format="reference" />
<attr name="front_image" format="reference" />
<attr name="thumb_image" format="reference" />
<attr name="indicate_image" format="reference" />
</resources>
這裏每一個attr屬性代表了哪些內容需要根據主題不同而更換,比如popupwindow_bg,即彈出窗口的背景色等等,然後在styles文件文件中指定各個主題下,這些值分別對應哪個具體的值,styles中相關代碼如下
<style name="Theme_blue">
<item name="theme_color">@color/blue</item>
<item name="popupwindow_bg">@drawable/popupwindow_bg</item>
<item name="dialogactivity_bg">@drawable/dialogactivity_bg</item>
<item name="btn_submit_bg">@drawable/btn_submit_bg</item>
<item name="seekbar_progress_bg">@drawable/seekbar_progress_bg</item>
<item name="play_image">@mipmap/play</item>
<item name="next_image">@mipmap/next</item>
<item name="front_image">@mipmap/front</item>
<item name="thumb_image">@mipmap/seekbar_thumb</item>
<item name="indicate_image">@mipmap/play_small</item>
</style>
<style name="Theme_purple">
<item name="theme_color">@color/purple</item>
<item name="popupwindow_bg">@drawable/popupwindow_bg_purple</item>
<item name="dialogactivity_bg">@drawable/dialogactivity_bg_purple</item>
<item name="btn_submit_bg">@drawable/btn_submit_bg_purple</item>
<item name="seekbar_progress_bg">@drawable/seekbar_progress_bg_purple</item>
<item name="play_image">@mipmap/play_purple</item>
<item name="next_image">@mipmap/next_purple</item>
<item name="front_image">@mipmap/front_purple</item>
<item name="thumb_image">@mipmap/seekbar_thumb_purple</item>
<item name="indicate_image">@mipmap/play_small_purple</item>
</style>
<style name="Theme_green">
<item name="theme_color">@color/green</item>
<item name="popupwindow_bg">@drawable/popupwindow_bg_green</item>
<item name="dialogactivity_bg">@drawable/dialogactivity_bg_green</item>
<item name="btn_submit_bg">@drawable/btn_submit_bg_green</item>
<item name="seekbar_progress_bg">@drawable/seekbar_progress_bg_green</item>
<item name="play_image">@mipmap/play_green</item>
<item name="next_image">@mipmap/next_green</item>
<item name="front_image">@mipmap/front_green</item>
<item name="thumb_image">@mipmap/seekbar_thumb_green</item>
<item name="indicate_image">@mipmap/play_small_green</item>
</style>
<style name="Theme_red">
<item name="theme_color">@color/red</item>
<item name="popupwindow_bg">@drawable/popupwindow_bg_red</item>
<item name="dialogactivity_bg">@drawable/dialogactivity_bg_red</item>
<item name="btn_submit_bg">@drawable/btn_submit_bg_red</item>
<item name="seekbar_progress_bg">@drawable/seekbar_progress_bg_red</item>
<item name="play_image">@mipmap/play_red</item>
<item name="next_image">@mipmap/next_red</item>
<item name="front_image">@mipmap/front_red</item>
<item name="thumb_image">@mipmap/seekbar_thumb_red</item>
<item name="indicate_image">@mipmap/play_small_red</item>
</style>
可以很清楚的看到,我設置了四個主題,每個主題中,我都對attrs中定義的屬性進行了具體的賦值,然後怎麼使用呢,舉個例子,比如我現在需要讓popupwindow的背景色隨主題改變而更換,那麼在popupwindow的佈局中,設置其background屬性爲如下即可
android:background="?attr/popupwindow_bg"
其他屬性的使用方法同理,然後我們如何來讓用戶設置主題呢,可以寫一個dialog,也可popupwindow,不過我這裏爲了學習一下樣式爲dialog的activity,便採用了這種方式,最後效果如下
看上去就像一個dialog,其實是一個activity,然後在這裏根據用戶的選擇,來設置不同的主題,然後拿到主題的類型之後,在代碼中根據這個值去判斷應該顯示哪個主題,相關代碼如下
// 主題設置
string_theme = sharedPreferences.getString("theme_select", "blue");
if (string_theme.equals("blue")) {
setTheme(R.style.Theme_blue);
} else if (string_theme.equals("purple")) {
setTheme(R.style.Theme_purple);
} else if (string_theme.equals("green")) {
setTheme(R.style.Theme_green);
} else {
setTheme(R.style.Theme_red);
}
setContentView(R.layout.activity_main);
記住一定要在setContentView方法之前調用,具體細節各方面由於代碼比較散,不方便貼,可以去源碼裏看我是怎麼設置的,最終四個主題下的主界面效果如下
當然這個APP裏,還有很多其他的細節,諸如,控制當前播放的列表項爲不同顏色,頂部顯示歌曲名字的彩色TextView等,這些可以直接去看源碼,實現的方法也不難,歡迎訪問源碼!!
源碼下載
由於考慮到大家可能沒有積分,我把源碼重新傳到了百度雲,這樣大家可以免費下載學習,鏈接和提取碼如下:
鏈接: https://pan.baidu.com/s/1KNxJvsE6XTIi3JkEBgCNgw 提取碼: 4xhi