音乐播放器 EasyMusic (一)

EasyMusicandroid

 

一. 代码获取git

  github 上连接为 https://github.com/VincentWYJ/EasyMusic, 感兴趣的朋友能够同步下来看, 欢迎提出宝贵的意见.github

1. clone数组

  进入连接后点击下图黑圈内按钮获取 git 地址, 而后经过命令将代码克隆到本地, 好比存储在 E 盘根目录的命令:网络

  cd E:/eclipse

  git clone https://github.com/VincentWYJ/EasyMusic.git, 标红部分就是项目代码 git 地址.ide

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 }

 

四. 总结与讨论

  目前该应用只是实现了一些最基本的功能, 还有不少事情及细节须要处理, 如应用界面不可见, 转屏, 其余应用须要音频焦点, 网络音频文件的获取/播放, 界面设计的优化布局组件的调整等等. 欢迎感兴趣或者有经验的朋友可以一块儿交流, 感谢.