EasyMusic
一. 代碼獲取
github 上鏈接為 https://github.com/VincentWYJ/EasyMusic, 感興趣的朋友可以同步下來看, 歡迎提出寶貴的意見.
1. clone
進入鏈接后點擊下圖黑圈內按鈕獲取 git 地址, 然后通過命令將代碼克隆到本地, 比如存儲在 E 盤根目錄的命令:
cd E:/
git clone https://github.com/VincentWYJ/EasyMusic.git, 標紅部分就是項目代碼 git 地址.

2. download
直接點擊上圖紅圈內按鈕並根據提示下載代碼到指定文件夾.
二. 應用簡介
Easy Music 致力於打造一款簡單易用的音樂播放器, 目前實現了部分基本功能,大致情況為:
1. android 4.4.2 + eclipse;
2. 本地音樂檢索與播放;
3. 界面頂部提供三個按鈕 Music, Album, Artist;
4. 界面中間在應用啟動時默認顯示 Music 對應的全部歌曲信息列表, 點擊 Album 或 Artist 顯示所有專輯或歌手信息列表; 這里還是采用較傳統的做法--FrameLayout + Fragment (add or replace), 而目前主流的一般均利用 ViewPager 來裝載頁面以使其可以滑動, TableLayout 來實現頂部標題一欄 (按鈕), 結合標題下的指示橫條三者一起使用達到像網易新聞那樣的界面效果;
5. 點擊 Music 列表中的項直接播放對應的音樂文件, 點擊 Album 或者 Artist 列表中的項進一步顯示對應的歌曲信息列表, 同理此時點擊某項可播放;
6. 界面下方包含了三部分, 一為播放進度顯示條, 還可拖動來調節播放位置; 二為時間與歌名的顯示; 三為上一首, 下一首, 播放/暫停按鈕;
7. 若用戶刪除某個音樂文件且沒有重新獲取歌曲列表, 當其點擊該首歌時給出提出文件已刪提示並將該歌曲項從列表中移除;
8. 為了方便查找, 對列表中的項進行了升序排列;
先來看一下界面, Music, Album, Artist 對應的信息列表分別如下左中右圖:

三. 項目分析
由於目前為止布局及按鈕點擊效果文件比較簡單, 所以接下來只對項目中關鍵的類或代碼進行分析.
1. 歌曲信息類 MusicInfo
該類記錄了一首歌曲幾乎全部的信息, 常用的有歌曲名 mTitle, 專輯名 mAlbum, 歌手名 mArtist 及歌曲存放路徑 mPath.
下面給出類構造函數和元素--歌曲名獲取函數, 其他信息獲取函數可從同步的代碼文件中進行查看.
1 public MusicInfo(Bundle bundle) { 2 mId = bundle.getInt(MediaStore.Audio.Media._ID); 3 mTitle = bundle.getString(MediaStore.Audio.Media.TITLE); 4 mTitleKey = bundle.getString(MediaStore.Audio.Media.TITLE_KEY); 5 mArtist = bundle.getString(MediaStore.Audio.Media.ARTIST); 6 mArtistKey = bundle.getString(MediaStore.Audio.Media.ARTIST_KEY); 7 mComposer = bundle.getString(MediaStore.Audio.Media.COMPOSER); 8 mAlbum = bundle.getString(MediaStore.Audio.Media.ALBUM); 9 mAlbumKey = bundle.getString(MediaStore.Audio.Media.ALBUM_KEY); 10 mDisplayName = bundle.getString(MediaStore.Audio.Media.DISPLAY_NAME); 11 mYear = bundle.getInt(MediaStore.Audio.Media.YEAR); 12 mMimeType = bundle.getString(MediaStore.Audio.Media.MIME_TYPE); 13 mPath = bundle.getString(MediaStore.Audio.Media.DATA); 14 15 mArtistId = bundle.getInt(MediaStore.Audio.Media.ARTIST_ID); 16 mAlbumId = bundle.getInt(MediaStore.Audio.Media.ALBUM_ID); 17 mTrack = bundle.getInt(MediaStore.Audio.Media.TRACK); 18 mDuration = bundle.getInt(MediaStore.Audio.Media.DURATION); 19 mSize = bundle.getInt(MediaStore.Audio.Media.SIZE); 20 21 isRingtone = bundle.getInt(MediaStore.Audio.Media.IS_RINGTONE) == 1; 22 isPodcast = bundle.getInt(MediaStore.Audio.Media.IS_PODCAST) == 1; 23 isAlarm = bundle.getInt(MediaStore.Audio.Media.IS_ALARM) == 1; 24 isMusic = bundle.getInt(MediaStore.Audio.Media.IS_MUSIC) == 1; 25 isNotification = bundle.getInt(MediaStore.Audio.Media.IS_NOTIFICATION) == 1; 26 }
1 public String getTitle () { 2 return mTitle; 3 }
2. 歌曲獲取類 GetMusicInfoList
類的定義只有兩個部分, 一是需要查詢的歌曲信息-- key 數組, 二是根據 key 數組從設備中讀取音樂文件信息並返回以 MusicInfo 對象為元素的 List 對象. 相應的代碼如下:
1 public static final String[] MUSIC_KEYS = new String[]{ 2 MediaStore.Audio.Media._ID, 3 MediaStore.Audio.Media.TITLE, 4 MediaStore.Audio.Media.TITLE_KEY, 5 MediaStore.Audio.Media.ARTIST, 6 MediaStore.Audio.Media.ARTIST_ID, 7 MediaStore.Audio.Media.ARTIST_KEY, 8 MediaStore.Audio.Media.COMPOSER, 9 MediaStore.Audio.Media.ALBUM, 10 MediaStore.Audio.Media.ALBUM_ID, 11 MediaStore.Audio.Media.ALBUM_KEY, 12 MediaStore.Audio.Media.DISPLAY_NAME, 13 MediaStore.Audio.Media.DURATION, 14 MediaStore.Audio.Media.SIZE, 15 MediaStore.Audio.Media.YEAR, 16 MediaStore.Audio.Media.TRACK, 17 MediaStore.Audio.Media.IS_RINGTONE, 18 MediaStore.Audio.Media.IS_PODCAST, 19 MediaStore.Audio.Media.IS_ALARM, 20 MediaStore.Audio.Media.IS_MUSIC, 21 MediaStore.Audio.Media.IS_NOTIFICATION, 22 MediaStore.Audio.Media.MIME_TYPE, 23 MediaStore.Audio.Media.DATA 24 }; 25 26 public static List<MusicInfo> getMusicList(Context context, String selection, String[] selectionArgs, String sortOrder) { 27 List<MusicInfo> audioList = new ArrayList<MusicInfo>(); 28 29 ContentResolver resolver = context.getContentResolver(); 30 31 Cursor cursor = resolver.query( 32 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 33 MUSIC_KEYS, 34 selection, 35 selectionArgs, 36 sortOrder); 37 38 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 39 Bundle bundle = new Bundle (); 40 41 for (int i = 0; i < MUSIC_KEYS.length; i++) { 42 final String key = MUSIC_KEYS[i]; 43 final int columnIndex = cursor.getColumnIndex(key); 44 final int type = cursor.getType(columnIndex); 45 switch (type) { 46 case Cursor.FIELD_TYPE_BLOB: 47 break; 48 case Cursor.FIELD_TYPE_FLOAT: 49 float floatValue = cursor.getFloat(columnIndex); 50 bundle.putFloat(key, floatValue); 51 break; 52 case Cursor.FIELD_TYPE_INTEGER: 53 int intValue = cursor.getInt(columnIndex); 54 bundle.putInt(key, intValue); 55 break; 56 case Cursor.FIELD_TYPE_NULL: 57 break; 58 case Cursor.FIELD_TYPE_STRING: 59 String strValue = cursor.getString(columnIndex); 60 bundle.putString(key, strValue); 61 break; 62 } 63 } 64 65 MusicInfo audio = new MusicInfo(bundle); 66 audioList.add(audio); 67 } 68 69 cursor.close(); 70 71 return audioList; 72 }
獲取的歌曲類型及信息可以根據實際需求進行調整, 如類型可以利用過濾機制只讀取 mp3 文件, 信息可以只讀取上面提到的歌曲名等必要的四個或者五個, 這樣在音樂文件很多的情況下可以減少查詢時間也方便在播放過程中找到目標歌曲.
方法 getMusicList() 除返回最終 List<MusicInfo> 外, 有兩點需要注意:
2.1 方法 query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 的五個參數:
2.1.1 uri --需要查詢的資源類型, 音樂或音頻一般為 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
2.1.2 protection --需要獲取的信息 key 數組, 如上面定義的 MUSIC_KEYS;
2.1.3 selection --信息選擇條件 (或者過濾機制條件), 若設置為 null 則表示希望獲取 uri 對應的所有文件;
2.1.4 selectionArgs --當需要為 selecton 傳遞額外參數值時使用, 不需要則設置為null;
2.1.5 sortOrder --給獲取的結果信息列表設置排序機制;
詳細的用法及參數設置會在后面被調用時給出.
2.2 for 循環在將 cursor 中的信息寫入 bundle 對象前進行了不同類型的判斷, 只獲取了 Integer, Int, String三種類型的值.
現在可以依靠上面定義的兩個類來獲取設備中的音樂文件信息了, 接下來需要做的就是調用那些方法並把獲得的結果進行處理后顯示在界面上, 還有就是音樂播放的控制. 第二部分應用簡介中提到界面布局中間用於顯示歌曲信息列表, 從給出的三張效果圖也可以看出, 中間部分顯示的內容改變時, 頂部和底部的布局組件其實是沒有重新繪制的. 中間布局使用了 FrameLayout, 而 Music, Album, Artist 三個按鈕點擊后分別會生成三個 Fragment 對象 (當然肯定是繼承它的子類), 然后利用 FragmentManeger 類的方法將其放置在 FrameLayout 中.
3. 主類 EasyMusicMainActivity
作為核心控制類要完成的事情很多, 主要有以下6點. 像按鈕響應這些常規的實現就啰嗦了, 只對關鍵的部分給出代碼講解.
3.1 頂部三個按鈕的點擊響應;
3.2 中間布局內容的切換, 這取決於上一步的操作;
3.3 中間信息列表項的點擊響應;
3.4 文件從其他途徑被刪除后, 點擊列表中對應項的處理;
3.5 底部播放信息的顯示與更新;
3.6 底部播放控制按鈕的點擊響應;
1 public static List<MusicInfo> musicInfos = null; 2 public static List<Map<String, Object>>musicMapList = null; 3 public static SimpleAdapter musicInfoListAdapter = null; 4 5 public static void getMusicInfos(String selection, String[] selectionArgs, String sortOrder){ 6 musicInfos = GetMusicInfoList.getMusicList(mContext, selection, selectionArgs, sortOrder); 7 8 if(isGetMusicListFlag){ 9 musicMapList.clear(); 10 11 for(int i=0;i<musicInfos.size();++i){ 12 Map<String, Object> map = new HashMap<String, Object>(); 13 map.put("title", musicInfos.get(i).getTitle()); 14 map.put("artist", musicInfos.get(i).getArtist()); 15 map.put("album", musicInfos.get(i).getAlbum()); 16 float duration = (float) (musicInfos.get(i).getDuration()/60.0/1000.0); 17 int pre = (int)duration; 18 float suf = (duration-pre)*60; 19 map.put("duration",String.valueOf(pre)+":"+decimalFormat.format(suf)); 20 musicMapList.add(map); 21 } 22 } 23 } 24 25 public static void getMusicInfoListAdapter(){ 26 musicInfoListAdapter = new SimpleAdapter(mContext, musicMapList, R.layout.musicinfo_layout, 27 new String[]{"title", "artist", "duration"}, 28 new int[]{R.id.left_top, R.id.left_bottom, R.id.right}); 29 }
像音樂信息列表這種數據操作一般都會用 ListView 組件來進行加載, 而數據加載需要借助 BaseAdapter 的子類對象, 后者又需要利用 List 等存儲數據的類對象來進行初始化. 注意方法 getMusicInfoListAdapter() 中初始化 musicInfoListAdapter 對象時用的布局文件是自定義的 R.layout.musicinfo_layout, 因為這里涉及到了三個元素, 而 android 自身提供的布局文件多為一元或者二元. R.layout.musicinfo_layout 內容如下, 格局為左側上下各一個元素, 右側居中一個元素.
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:orientation="horizontal" 6 android:gravity="center_vertical" > 7 8 <LinearLayout 9 android:layout_width="0dp" 10 android:layout_height="wrap_content" 11 android:layout_weight="1" 12 android:layout_marginTop="5dp" 13 android:layout_marginBottom="5dp" 14 android:layout_marginStart="5dp" 15 android:orientation="vertical" 16 android:gravity="start|center" > 17 18 <TextView android:id="@+id/left_top" 19 style="@style/InfoTopTextView" /> 20 21 <TextView android:id="@+id/left_bottom" 22 style="@style/InfoBottomTextView" /> 23 24 </LinearLayout> 25 26 <TextView android:id="@+id/right" 27 android:layout_width="wrap_content" 28 android:layout_height="match_parent" 29 android:layout_marginEnd="5dp" 30 android:textSize="@dimen/music_info_leftbottom_size" 31 android:gravity="center" /> 32 33 </LinearLayout>
對照上面第一張效果圖--歌曲信息列表來看, 三個元素分別顯示了歌曲名, 歌手名, 歌曲時長. 而后兩張則分別顯示了專輯名及歌曲數和歌手名及歌曲數.
這兩個方法放在主類中, 而不是單獨放到 Music 對應的 Fragment 中, 是因為除了 Music 模塊要獲取歌曲信息外, 在點擊了 Album 或者 Artist 對應的 Fragment 信息列表中的項時也需要獲取歌曲信息.
1 switch (localMusicType) { 2 case R.id.local_music_title: 3 listFragment = new LocalMusicListFragment(); 4 break; 5 case R.id.local_album_title: 6 listFragment = new LocalAlbumListFragment(); 7 break; 8 case R.id.local_artist_title: 9 listFragment = new LocalArtistListFragment(); 10 break; 11 default: 12 break; 13 } 14 15 FragmentTransaction fTransaction = getFragmentManager().beginTransaction(); 16 fTransaction.add(R.id.musicinfo_list_fragment, listFragment); 17 fTransaction.setTransition(FragmentTransaction.TRANSIT_NONE); 18 fTransaction.commit();
這段代碼作為按鈕 Music, Album, Artist 點擊響應的部分代碼, 用於切換中間布局的 Fragment 內容, 三種類別分別對應LocalMusicListFragment, LocalAlbumListFragment, LocalArtistListFragment.
1 public void setSeekBarOnClickListener(){ 2 musicPlaySeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { 3 4 @Override 5 public void onStopTrackingTouch(SeekBar seekBar) { 6 if(mediaPlayer != null){ 7 mediaPlayer.seekTo(seekBar.getProgress()); 8 } 9 } 10 11 @Override 12 public void onStartTrackingTouch(SeekBar seekBar) { 13 } 14 15 @Override 16 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 17 float duration = progress/60.0f/1000.0f; 18 int pre = (int)duration; 19 int suf = (int)((duration-pre)*60); 20 musicTimePlay.setText(String.valueOf(pre)+":"+decimalFormat.format(suf)); 21 } 22 }); 23 }
24 public static DecimalFormat decimalFormat = new DecimalFormat("00");
這段代碼給播放進度組件 musicPlayseekBar 設置位置改變監聽器, 主要做了兩件事情:
3.7 手動調節其指示點位置時更新音頻播放類對象 mediaPlayer 的播放位置;
3.8 指示點變化時更新已播放時間組件 musicTimePlay 的顯示內容, 利用格式對象 decimalFormat 來統一秒數總是以兩位顯示;
1 public void setSeekBarMoveListener(){ 2 new Thread(new Runnable() { 3 4 @Override 5 public void run() { 6 while (true) { 7 try { 8 Thread.sleep(500); 9 if(isMusicPlaying){ 10 musicPlaySeekBar.setProgress(mediaPlayer.getCurrentPosition()); 11 } 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 } 17 }).start(); 18 }
很遺憾 MediaPlayer 類除了可以監聽當前音頻文件播放完畢意外, 沒有再提供其他的監聽器了 (比如監聽播放進度). 所以只能采用例如上面代碼模塊那樣線程+定時的方式來使進度條指示器位置的不斷更新, 至於多久更新一次就看實際需求了, 間隔越小越流暢. 注意若以播放位置為進度條的目標位置, 在初始化進度條最大值時也必須為音頻大小 (毫秒為單位的 int 型數值).
1 public void MusicPlayControl(View playView){ 2 int id = playView.getId(); 3 switch (id) { 4 case R.id.music_play_next: 5 MusicPlay((positionPlay+1)%musicInfos.size()); 6 break; 7 case R.id.music_play_pre: 8 MusicPlay((musicInfos.size()+positionPlay-1)%musicInfos.size()); 9 break; 10 case R.id.music_play_pause: 11 if(isMusicPlaying){ 12 isMusicPlaying = false; 13 mediaPlayer.pause(); 14 musicPlayPause.setBackgroundResource(R.drawable.music_to_pause); 15 }else{ 16 if(mediaPlayer != null){ 17 isMusicPlaying = true; 18 mediaPlayer.start(); 19 musicPlayPause.setBackgroundResource(R.drawable.music_to_start); 20 }else{ 21 MusicPlay(positionPlay); 22 } 23 } 24 break; 25 default: 26 break; 27 } 28 } 29 30 public static void MusicPlay(int position){ 31 isMusicPlaying = false; 32 int totalTime = musicInfos.get(position).getDuration(); 33 positionPlay = position; 34 musicPlaySeekBar.setMax(totalTime); 35 songPath = musicInfos.get(position).getPath(); 36 File songFile = new File(songPath); 37 if(!songFile.exists()){ 38 Toast.makeText(mContext, "The music file doesn't exists, already updated music list.", Toast.LENGTH_SHORT).show(); 39 //only remove the special file 40 musicInfos.remove(position); 41 musicMapList.remove(position); 42 musicInfoListAdapter.notifyDataSetChanged(); 43 return; 44 } 45 uri = Uri.fromFile(songFile); 46 try { 47 if(mediaPlayer != null){ 48 mediaPlayer.stop(); 49 mediaPlayer.release(); 50 mediaPlayer = null; 51 } 52 mediaPlayer = new MediaPlayer(); 53 mediaPlayer.setDataSource(mContext, uri); 54 mediaPlayer.prepare(); 55 mediaPlayer.start(); 56 isMusicPlaying = true; 57 setMusicViewInfos(); 58 } catch (IllegalStateException e) { 59 e.printStackTrace(); 60 } catch (IOException e) { 61 e.printStackTrace(); 62 } 63 }
方法 MusicPlayControl() 作為按鈕上一首, 下一首, 暫停/播放的響應, 主要借助控制方法 MusicPlay() 來進行相應的操作. 怎么對點擊的音樂進行播放很簡單, 這里需要注意的是代碼中的那一段 if(...){...}, 即如果某歌曲文件在列表生成后被其他操作刪除了, 這里可以判斷出來給出文件刪除提示並立刻進行更新列表, 否則用戶體驗會很不好, 雖然重新啟動應用或者重新獲取列表也可以達到更新的目的.
注意如果只是Adapter對應的數據源對應中的某項數據改變了, 只需要調用 notifyDataSetChanged() 方法即可讓 ListView 組件進行顯示內容的更新.
1 public class SetElementComparator extends Collator 2 { 3 @Override 4 public int compare(String s1, String s2) 5 { 6 return s1.compareTo(s2); 7 } 8 9 @Override 10 public CollationKey getCollationKey(String source) 11 { 12 return null; 13 } 14 15 @Override 16 public int hashCode() 17 { 18 return 0; 19 } 20 }
前面說過獲取的歌曲信息是升序排列的, 但是對於 Album 和 Artist 這兩種類別, 當一個專輯或者一個歌手有多首歌時, 是不該讓列表中出現多個相同專輯項的. 剛開始很粗心采用 Set<String> 對象來存儲專輯名或者歌手名, 雖然結果列表中元素重復是沒有了, 但會發現原本該升序的信息卻亂了. 查了資料才發現原來集合類 Set 在調用 add() 方法添加元素時是根據其 hashCode 值來決定元素位置的, 所以改用 TreeSet. 但是 TreeSet 好像對中文也不是 "敏感" 的, 解決辦法是自定義繼承 Collator 的類, 在創建實例時傳入 Locale.China 參數, 然后將實例對象傳入 TreeSet<String> 實例方法.
4. 專輯類 LocalAlbumListFragment
1 public static Collator collator = SetElementComparator.getInstance(Locale.CHINA); //定義在主類EasyMusicMainActivity中 2 3 public void getAlbumInfos(String selection, String[] selectionArgs, String sortOrder){ 4 EasyMusicMainActivity.getMusicInfos(selection, selectionArgs, sortOrder); 5 6 albumMapList.clear(); 7 8 Set<String>albumNameSet = new TreeSet<String>(EasyMusicMainActivity.collator); 9 for(int i=0; i<EasyMusicMainActivity.musicInfos.size(); ++i){ 10 albumName = EasyMusicMainActivity.musicInfos.get(i).getAlbum(); 11 albumNameSet.add(albumName); 12 } 13 14 int albumCountArray[] = new int[albumNameSet.size()]; 15 int index = 0; 16 for(Iterator<String>iter = albumNameSet.iterator(); iter.hasNext();){ 17 String albumNameInSet = iter.next(); 18 String albumArtist = null; 19 for(int i=0; i<EasyMusicMainActivity.musicInfos.size(); ++i){ 20 albumName = EasyMusicMainActivity.musicInfos.get(i).getAlbum(); 21 if(albumNameInSet.equals(albumName)){ 22 albumCountArray[index] += 1; 23 albumArtist = EasyMusicMainActivity.musicInfos.get(i).getArtist(); 24 } 25 } 26 27 Map<String, Object> map = new HashMap<String, Object>(); 28 map.put("album", albumNameInSet); 29 map.put("count", albumCountArray[index]+" 首 - "+albumArtist); 30 albumMapList.add(map); 31 ++index; 32 } 33 } 34 35 public void getAlbumInfoListAdapter(){ 36 EasyMusicMainActivity.musicInfoListAdapter = new SimpleAdapter(getActivity(), albumMapList, R.layout.musicinfo_layout, 37 new String[]{"album", "count"}, 38 new int[]{R.id.left_top, R.id.left_bottom}); 39 }
獲取 Album 列表信息和歌曲不同的是, 需要去除重復並統計每張專輯的歌曲數, Adapter 對象初始化只傳入專輯名和歌曲數目, 布局右側的欄位不顯示任何信息.
1 musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter); 2 musicInfoList.setOnItemClickListener(new OnItemClickListener() { 3 4 @Override 5 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 6 // TODO Auto-generated method stub 7 EasyMusicMainActivity.isGetMusicListFlag = true; 8 albumName = (String) albumMapList.get(position).get("album"); 9 EasyMusicMainActivity.getMusicInfos(MediaStore.Audio.Media.ALBUM+"=?", new String[]{albumName}, EasyMusicMainActivity.musicSortOrder); 10 EasyMusicMainActivity.getMusicInfoListAdapter(); 11 musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter); 12 musicInfoList.setOnItemClickListener(new OnItemClickListener() { 13 14 @Override 15 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 16 // TODO Auto-generated method stub 17 EasyMusicMainActivity.MusicPlay(position); 18 } 19 }); 20 } 21 }); 22 }
信息列表項的點擊響應也會有所不同, 點擊專輯名后會需要獲取專輯對應的所有歌曲, 然后才是最終的歌曲信息列表. 這里在獲取歌曲時傳入了 selection 和 selectionArgs參數.
5. 歌手類 LocalArtistListFragment
Artist 類別也是同樣的實現方式, 代碼如下.
1 public void getArtistInfos(String selection, String[] selectionArgs, String sortOrder){ 2 EasyMusicMainActivity.getMusicInfos(selection, selectionArgs, sortOrder); 3 4 artistMapList.clear(); 5 6 Set<String>artistNameSet = new TreeSet<String>(EasyMusicMainActivity.collator); 7 for(int i=0; i<EasyMusicMainActivity.musicInfos.size(); ++i){ 8 artistName = EasyMusicMainActivity.musicInfos.get(i).getArtist(); 9 artistNameSet.add(artistName); 10 } 11 12 int artistCountArray[] = new int[artistNameSet.size()]; 13 int index = 0; 14 for(Iterator<String>iter = artistNameSet.iterator(); iter.hasNext();){ 15 String artistNameInSet = iter.next(); 16 for(int i=0; i<EasyMusicMainActivity.musicInfos.size(); ++i){ 17 artistName = EasyMusicMainActivity.musicInfos.get(i).getArtist(); 18 if(artistNameInSet.equals(artistName)){ 19 artistCountArray[index] += 1; 20 } 21 } 22 23 Map<String, Object> map = new HashMap<String, Object>(); 24 map.put("artist", artistNameInSet); 25 map.put("count", artistCountArray[index]+" 首 "); 26 artistMapList.add(map); 27 ++index; 28 } 29 } 30 31 public void getArtistInfoListAdapter(){ 32 EasyMusicMainActivity.musicInfoListAdapter = new SimpleAdapter(getActivity(), artistMapList, R.layout.musicinfo_layout, 33 new String[]{"artist", "count"}, 34 new int[]{R.id.left_top, R.id.left_bottom}); 35 } 36 37 public void initArtistListView(){ 38 musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter); 39 musicInfoList.setOnItemClickListener(new OnItemClickListener() { 40 41 @Override 42 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 43 // TODO Auto-generated method stub 44 EasyMusicMainActivity.isGetMusicListFlag = true; 45 artistName = (String) artistMapList.get(position).get("artist"); 46 EasyMusicMainActivity.getMusicInfos(MediaStore.Audio.Media.ARTIST+"=?", new String[]{artistName}, EasyMusicMainActivity.musicSortOrder); 47 EasyMusicMainActivity.getMusicInfoListAdapter(); 48 musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter); 49 musicInfoList.setOnItemClickListener(new OnItemClickListener() { 50 51 @Override 52 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 53 // TODO Auto-generated method stub 54 EasyMusicMainActivity.MusicPlay(position); 55 } 56 }); 57 } 58 }); 59 }
6. 歌曲類 LocalMusicListFragment
Music 類別最簡單, 獲取的結果就是最終列表信息.
1 public void initListView(){ 2 musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter); 3 musicInfoList.setOnItemClickListener(new OnItemClickListener() { 4 5 @Override 6 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 7 // TODO Auto-generated method stub 8 EasyMusicMainActivity.MusicPlay(position); 9 } 10 }); 11 }
四. 總結與討論
目前該應用只是實現了一些最基本的功能, 還有很多事情及細節需要處理, 如應用界面不可見, 轉屏, 其他應用需要音頻焦點, 網絡音頻文件的獲取/播放, 界面設計的優化與布局組件的調整等等. 歡迎感興趣或者有經驗的朋友能夠一起交流, 感謝.
