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.
下面给出类构造函数和元素--歌曲名获取函数, 其他信息获取函数可从同步的代码文件中进行查看.
public MusicInfo(Bundle bundle) {
mId = bundle.getInt(MediaStore.Audio.Media._ID);
mTitle = bundle.getString(MediaStore.Audio.Media.TITLE);
mTitleKey = bundle.getString(MediaStore.Audio.Media.TITLE_KEY);
mArtist = bundle.getString(MediaStore.Audio.Media.ARTIST);
mArtistKey = bundle.getString(MediaStore.Audio.Media.ARTIST_KEY);
mComposer = bundle.getString(MediaStore.Audio.Media.COMPOSER);
mAlbum = bundle.getString(MediaStore.Audio.Media.ALBUM);
mAlbumKey = bundle.getString(MediaStore.Audio.Media.ALBUM_KEY);
mDisplayName = bundle.getString(MediaStore.Audio.Media.DISPLAY_NAME);
mYear = bundle.getInt(MediaStore.Audio.Media.YEAR);
mMimeType = bundle.getString(MediaStore.Audio.Media.MIME_TYPE);
mPath = bundle.getString(MediaStore.Audio.Media.DATA); mArtistId = bundle.getInt(MediaStore.Audio.Media.ARTIST_ID);
mAlbumId = bundle.getInt(MediaStore.Audio.Media.ALBUM_ID);
mTrack = bundle.getInt(MediaStore.Audio.Media.TRACK);
mDuration = bundle.getInt(MediaStore.Audio.Media.DURATION);
mSize = bundle.getInt(MediaStore.Audio.Media.SIZE); isRingtone = bundle.getInt(MediaStore.Audio.Media.IS_RINGTONE) == 1;
isPodcast = bundle.getInt(MediaStore.Audio.Media.IS_PODCAST) == 1;
isAlarm = bundle.getInt(MediaStore.Audio.Media.IS_ALARM) == 1;
isMusic = bundle.getInt(MediaStore.Audio.Media.IS_MUSIC) == 1;
isNotification = bundle.getInt(MediaStore.Audio.Media.IS_NOTIFICATION) == 1;
}
public String getTitle () {
return mTitle;
}
2. 歌曲获取类 GetMusicInfoList
类的定义只有两个部分, 一是需要查询的歌曲信息-- key 数组, 二是根据 key 数组从设备中读取音乐文件信息并返回以 MusicInfo 对象为元素的 List 对象. 相应的代码如下:
public static final String[] MUSIC_KEYS = new String[]{
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.TITLE_KEY,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ARTIST_ID,
MediaStore.Audio.Media.ARTIST_KEY,
MediaStore.Audio.Media.COMPOSER,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.ALBUM_KEY,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.SIZE,
MediaStore.Audio.Media.YEAR,
MediaStore.Audio.Media.TRACK,
MediaStore.Audio.Media.IS_RINGTONE,
MediaStore.Audio.Media.IS_PODCAST,
MediaStore.Audio.Media.IS_ALARM,
MediaStore.Audio.Media.IS_MUSIC,
MediaStore.Audio.Media.IS_NOTIFICATION,
MediaStore.Audio.Media.MIME_TYPE,
MediaStore.Audio.Media.DATA
}; public static List<MusicInfo> getMusicList(Context context, String selection, String[] selectionArgs, String sortOrder) {
List<MusicInfo> audioList = new ArrayList<MusicInfo>(); ContentResolver resolver = context.getContentResolver(); Cursor cursor = resolver.query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
MUSIC_KEYS,
selection,
selectionArgs,
sortOrder); for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
Bundle bundle = new Bundle (); for (int i = 0; i < MUSIC_KEYS.length; i++) {
final String key = MUSIC_KEYS[i];
final int columnIndex = cursor.getColumnIndex(key);
final int type = cursor.getType(columnIndex);
switch (type) {
case Cursor.FIELD_TYPE_BLOB:
break;
case Cursor.FIELD_TYPE_FLOAT:
float floatValue = cursor.getFloat(columnIndex);
bundle.putFloat(key, floatValue);
break;
case Cursor.FIELD_TYPE_INTEGER:
int intValue = cursor.getInt(columnIndex);
bundle.putInt(key, intValue);
break;
case Cursor.FIELD_TYPE_NULL:
break;
case Cursor.FIELD_TYPE_STRING:
String strValue = cursor.getString(columnIndex);
bundle.putString(key, strValue);
break;
}
} MusicInfo audio = new MusicInfo(bundle);
audioList.add(audio);
} cursor.close(); return audioList;
}
获取的歌曲类型及信息可以根据实际需求进行调整, 如类型可以利用过滤机制只读取 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 底部播放控制按钮的点击响应;
public static List<MusicInfo> musicInfos = null;
public static List<Map<String, Object>>musicMapList = null;
public static SimpleAdapter musicInfoListAdapter = null; public static void getMusicInfos(String selection, String[] selectionArgs, String sortOrder){
musicInfos = GetMusicInfoList.getMusicList(mContext, selection, selectionArgs, sortOrder); if(isGetMusicListFlag){
musicMapList.clear(); for(int i=0;i<musicInfos.size();++i){
Map<String, Object> map = new HashMap<String, Object>();
map.put("title", musicInfos.get(i).getTitle());
map.put("artist", musicInfos.get(i).getArtist());
map.put("album", musicInfos.get(i).getAlbum());
float duration = (float) (musicInfos.get(i).getDuration()/60.0/1000.0);
int pre = (int)duration;
float suf = (duration-pre)*60;
map.put("duration",String.valueOf(pre)+":"+decimalFormat.format(suf));
musicMapList.add(map);
}
}
} public static void getMusicInfoListAdapter(){
musicInfoListAdapter = new SimpleAdapter(mContext, musicMapList, R.layout.musicinfo_layout,
new String[]{"title", "artist", "duration"},
new int[]{R.id.left_top, R.id.left_bottom, R.id.right});
}
像音乐信息列表这种数据操作一般都会用 ListView 组件来进行加载, 而数据加载需要借助 BaseAdapter 的子类对象, 后者又需要利用 List 等存储数据的类对象来进行初始化. 注意方法 getMusicInfoListAdapter() 中初始化 musicInfoListAdapter 对象时用的布局文件是自定义的 R.layout.musicinfo_layout, 因为这里涉及到了三个元素, 而 android 自身提供的布局文件多为一元或者二元. R.layout.musicinfo_layout 内容如下, 格局为左侧上下各一个元素, 右侧居中一个元素.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical" > <LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_marginStart="5dp"
android:orientation="vertical"
android:gravity="start|center" > <TextView android:id="@+id/left_top"
style="@style/InfoTopTextView" /> <TextView android:id="@+id/left_bottom"
style="@style/InfoBottomTextView" /> </LinearLayout> <TextView android:id="@+id/right"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="5dp"
android:textSize="@dimen/music_info_leftbottom_size"
android:gravity="center" /> </LinearLayout>
对照上面第一张效果图--歌曲信息列表来看, 三个元素分别显示了歌曲名, 歌手名, 歌曲时长. 而后两张则分别显示了专辑名及歌曲数和歌手名及歌曲数.
这两个方法放在主类中, 而不是单独放到 Music 对应的 Fragment 中, 是因为除了 Music 模块要获取歌曲信息外, 在点击了 Album 或者 Artist 对应的 Fragment 信息列表中的项时也需要获取歌曲信息.
switch (localMusicType) {
case R.id.local_music_title:
listFragment = new LocalMusicListFragment();
break;
case R.id.local_album_title:
listFragment = new LocalAlbumListFragment();
break;
case R.id.local_artist_title:
listFragment = new LocalArtistListFragment();
break;
default:
break;
} FragmentTransaction fTransaction = getFragmentManager().beginTransaction();
fTransaction.add(R.id.musicinfo_list_fragment, listFragment);
fTransaction.setTransition(FragmentTransaction.TRANSIT_NONE);
fTransaction.commit();
这段代码作为按钮 Music, Album, Artist 点击响应的部分代码, 用于切换中间布局的 Fragment 内容, 三种类别分别对应LocalMusicListFragment, LocalAlbumListFragment, LocalArtistListFragment.
public void setSeekBarOnClickListener(){
musicPlaySeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override
public void onStopTrackingTouch(SeekBar seekBar) {
if(mediaPlayer != null){
mediaPlayer.seekTo(seekBar.getProgress());
}
} @Override
public void onStartTrackingTouch(SeekBar seekBar) {
} @Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
float duration = progress/60.0f/1000.0f;
int pre = (int)duration;
int suf = (int)((duration-pre)*60);
musicTimePlay.setText(String.valueOf(pre)+":"+decimalFormat.format(suf));
}
});
} public static DecimalFormat decimalFormat = new DecimalFormat("00");
这段代码给播放进度组件 musicPlayseekBar 设置位置改变监听器, 主要做了两件事情:
3.7 手动调节其指示点位置时更新音频播放类对象 mediaPlayer 的播放位置;
3.8 指示点变化时更新已播放时间组件 musicTimePlay 的显示内容, 利用格式对象 decimalFormat 来统一秒数总是以两位显示;
public void setSeekBarMoveListener(){
new Thread(new Runnable() { @Override
public void run() {
while (true) {
try {
Thread.sleep(500);
if(isMusicPlaying){
musicPlaySeekBar.setProgress(mediaPlayer.getCurrentPosition());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
很遗憾 MediaPlayer 类除了可以监听当前音频文件播放完毕意外, 没有再提供其他的监听器了 (比如监听播放进度). 所以只能采用例如上面代码模块那样线程+定时的方式来使进度条指示器位置的不断更新, 至于多久更新一次就看实际需求了, 间隔越小越流畅. 注意若以播放位置为进度条的目标位置, 在初始化进度条最大值时也必须为音频大小 (毫秒为单位的 int 型数值).
public void MusicPlayControl(View playView){
int id = playView.getId();
switch (id) {
case R.id.music_play_next:
MusicPlay((positionPlay+1)%musicInfos.size());
break;
case R.id.music_play_pre:
MusicPlay((musicInfos.size()+positionPlay-1)%musicInfos.size());
break;
case R.id.music_play_pause:
if(isMusicPlaying){
isMusicPlaying = false;
mediaPlayer.pause();
musicPlayPause.setBackgroundResource(R.drawable.music_to_pause);
}else{
if(mediaPlayer != null){
isMusicPlaying = true;
mediaPlayer.start();
musicPlayPause.setBackgroundResource(R.drawable.music_to_start);
}else{
MusicPlay(positionPlay);
}
}
break;
default:
break;
}
} public static void MusicPlay(int position){
isMusicPlaying = false;
int totalTime = musicInfos.get(position).getDuration();
positionPlay = position;
musicPlaySeekBar.setMax(totalTime);
songPath = musicInfos.get(position).getPath();
File songFile = new File(songPath);
if(!songFile.exists()){
Toast.makeText(mContext, "The music file doesn't exists, already updated music list.", Toast.LENGTH_SHORT).show();
//only remove the special file
musicInfos.remove(position);
musicMapList.remove(position);
musicInfoListAdapter.notifyDataSetChanged();
return;
}
uri = Uri.fromFile(songFile);
try {
if(mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(mContext, uri);
mediaPlayer.prepare();
mediaPlayer.start();
isMusicPlaying = true;
setMusicViewInfos();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
方法 MusicPlayControl() 作为按钮上一首, 下一首, 暂停/播放的响应, 主要借助控制方法 MusicPlay() 来进行相应的操作. 怎么对点击的音乐进行播放很简单, 这里需要注意的是代码中的那一段 if(...){...}, 即如果某歌曲文件在列表生成后被其他操作删除了, 这里可以判断出来给出文件删除提示并立刻进行更新列表, 否则用户体验会很不好, 虽然重新启动应用或者重新获取列表也可以达到更新的目的.
注意如果只是Adapter对应的数据源对应中的某项数据改变了, 只需要调用 notifyDataSetChanged() 方法即可让 ListView 组件进行显示内容的更新.
public class SetElementComparator extends Collator
{
@Override
public int compare(String s1, String s2)
{
return s1.compareTo(s2);
} @Override
public CollationKey getCollationKey(String source)
{
return null;
} @Override
public int hashCode()
{
return 0;
}
}
前面说过获取的歌曲信息是升序排列的, 但是对于 Album 和 Artist 这两种类别, 当一个专辑或者一个歌手有多首歌时, 是不该让列表中出现多个相同专辑项的. 刚开始很粗心采用 Set<String> 对象来存储专辑名或者歌手名, 虽然结果列表中元素重复是没有了, 但会发现原本该升序的信息却乱了. 查了资料才发现原来集合类 Set 在调用 add() 方法添加元素时是根据其 hashCode 值来决定元素位置的, 所以改用 TreeSet. 但是 TreeSet 好像对中文也不是 "敏感" 的, 解决办法是自定义继承 Collator 的类, 在创建实例时传入 Locale.China 参数, 然后将实例对象传入 TreeSet<String> 实例方法.
4. 专辑类 LocalAlbumListFragment
public static Collator collator = SetElementComparator.getInstance(Locale.CHINA); //定义在主类EasyMusicMainActivity中 public void getAlbumInfos(String selection, String[] selectionArgs, String sortOrder){
EasyMusicMainActivity.getMusicInfos(selection, selectionArgs, sortOrder); albumMapList.clear(); Set<String>albumNameSet = new TreeSet<String>(EasyMusicMainActivity.collator);
for(int i=0; i<EasyMusicMainActivity.musicInfos.size(); ++i){
albumName = EasyMusicMainActivity.musicInfos.get(i).getAlbum();
albumNameSet.add(albumName);
} int albumCountArray[] = new int[albumNameSet.size()];
int index = 0;
for(Iterator<String>iter = albumNameSet.iterator(); iter.hasNext();){
String albumNameInSet = iter.next();
String albumArtist = null;
for(int i=0; i<EasyMusicMainActivity.musicInfos.size(); ++i){
albumName = EasyMusicMainActivity.musicInfos.get(i).getAlbum();
if(albumNameInSet.equals(albumName)){
albumCountArray[index] += 1;
albumArtist = EasyMusicMainActivity.musicInfos.get(i).getArtist();
}
} Map<String, Object> map = new HashMap<String, Object>();
map.put("album", albumNameInSet);
map.put("count", albumCountArray[index]+" 首 - "+albumArtist);
albumMapList.add(map);
++index;
}
} public void getAlbumInfoListAdapter(){
EasyMusicMainActivity.musicInfoListAdapter = new SimpleAdapter(getActivity(), albumMapList, R.layout.musicinfo_layout,
new String[]{"album", "count"},
new int[]{R.id.left_top, R.id.left_bottom});
}
获取 Album 列表信息和歌曲不同的是, 需要去除重复并统计每张专辑的歌曲数, Adapter 对象初始化只传入专辑名和歌曲数目, 布局右侧的栏位不显示任何信息.
musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter);
musicInfoList.setOnItemClickListener(new OnItemClickListener() { @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
EasyMusicMainActivity.isGetMusicListFlag = true;
albumName = (String) albumMapList.get(position).get("album");
EasyMusicMainActivity.getMusicInfos(MediaStore.Audio.Media.ALBUM+"=?", new String[]{albumName}, EasyMusicMainActivity.musicSortOrder);
EasyMusicMainActivity.getMusicInfoListAdapter();
musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter);
musicInfoList.setOnItemClickListener(new OnItemClickListener() { @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
EasyMusicMainActivity.MusicPlay(position);
}
});
}
});
}
信息列表项的点击响应也会有所不同, 点击专辑名后会需要获取专辑对应的所有歌曲, 然后才是最终的歌曲信息列表. 这里在获取歌曲时传入了 selection 和 selectionArgs参数.
5. 歌手类 LocalArtistListFragment
Artist 类别也是同样的实现方式, 代码如下.
public void getArtistInfos(String selection, String[] selectionArgs, String sortOrder){
EasyMusicMainActivity.getMusicInfos(selection, selectionArgs, sortOrder); artistMapList.clear(); Set<String>artistNameSet = new TreeSet<String>(EasyMusicMainActivity.collator);
for(int i=0; i<EasyMusicMainActivity.musicInfos.size(); ++i){
artistName = EasyMusicMainActivity.musicInfos.get(i).getArtist();
artistNameSet.add(artistName);
} int artistCountArray[] = new int[artistNameSet.size()];
int index = 0;
for(Iterator<String>iter = artistNameSet.iterator(); iter.hasNext();){
String artistNameInSet = iter.next();
for(int i=0; i<EasyMusicMainActivity.musicInfos.size(); ++i){
artistName = EasyMusicMainActivity.musicInfos.get(i).getArtist();
if(artistNameInSet.equals(artistName)){
artistCountArray[index] += 1;
}
} Map<String, Object> map = new HashMap<String, Object>();
map.put("artist", artistNameInSet);
map.put("count", artistCountArray[index]+" 首 ");
artistMapList.add(map);
++index;
}
} public void getArtistInfoListAdapter(){
EasyMusicMainActivity.musicInfoListAdapter = new SimpleAdapter(getActivity(), artistMapList, R.layout.musicinfo_layout,
new String[]{"artist", "count"},
new int[]{R.id.left_top, R.id.left_bottom});
} public void initArtistListView(){
musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter);
musicInfoList.setOnItemClickListener(new OnItemClickListener() { @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
EasyMusicMainActivity.isGetMusicListFlag = true;
artistName = (String) artistMapList.get(position).get("artist");
EasyMusicMainActivity.getMusicInfos(MediaStore.Audio.Media.ARTIST+"=?", new String[]{artistName}, EasyMusicMainActivity.musicSortOrder);
EasyMusicMainActivity.getMusicInfoListAdapter();
musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter);
musicInfoList.setOnItemClickListener(new OnItemClickListener() { @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
EasyMusicMainActivity.MusicPlay(position);
}
});
}
});
}
6. 歌曲类 LocalMusicListFragment
Music 类别最简单, 获取的结果就是最终列表信息.
public void initListView(){
musicInfoList.setAdapter(EasyMusicMainActivity.musicInfoListAdapter);
musicInfoList.setOnItemClickListener(new OnItemClickListener() { @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
EasyMusicMainActivity.MusicPlay(position);
}
});
}
四. 总结与讨论
目前该应用只是实现了一些最基本的功能, 还有很多事情及细节需要处理, 如应用界面不可见, 转屏, 其他应用需要音频焦点, 网络音频文件的获取/播放, 界面设计的优化与布局组件的调整等等. 欢迎感兴趣或者有经验的朋友能够一起交流, 感谢.