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);
}
});
}

四. 总结与讨论

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

音乐播放器 EasyMusic (一)的更多相关文章

  1. SE Springer小组之《Spring音乐播放器》可行性研究报告三、四

    3 对现有系统的分析 由于本次可行性分析主要是建立在团队自行实现一个音乐软件的目标上,并不是在一个现有系统的基础上开发改进的新系统.因此这里将分析一款市面上已经存在的音乐软件(以下称为W音乐),并为之 ...

  2. 卡拉OK效果的实现-iOS音乐播放器

    自己编写的音乐播放器偶然用到这个模块,发现没有思路,而且上网搜了搜,关于这方面的文章不是很多,没找到满意的结果,然后自己也是想了想,最终实现了这种效果,想通了发现其实很简单. 直接上原理: 第一种: ...

  3. 小菜学习Winform(二)WMPLib实现音乐播放器

    前言 现在网上有很多的音乐播放器,但好像都不是.net平台做的,在.net中实现音乐文件的播放功能很简单,下面就简单实现下. SoundPlayer类 在.net提供了音乐文件的类:SoundPlay ...

  4. 【大结局】《从案例中学习JavaScript》之酷炫音乐播放器(四)

    这是之前写的用H5制作的音乐播放器,前三节其实已经做得差不多了,音轨的制作原理已经在上一节说明,不过一直还没有和音乐对接. 本章作为该系列的一个完结篇,我会专门把动态音轨的实现代码贴出来,demo地址 ...

  5. Andriod小项目——在线音乐播放器

    转载自: http://blog.csdn.net/sunkes/article/details/51189189 Andriod小项目——在线音乐播放器 Android在线音乐播放器 从大一开始就已 ...

  6. Android开发6:Service的使用(简单音乐播放器的实现)

    前言 啦啦啦~各位好久不见啦~博主最近比较忙,而且最近一次实验也是刚刚结束~ 好了不废话了,直接进入我们这次的内容~ 在这篇博文里我们将学习Service(服务)的相关知识,学会使用 Service ...

  7. android 音乐播放器

    本章以音乐播放器为载体,介绍android开发中,通知模式Notification应用.主要涉及知识点Notification,seekbar,service. 1.功能需求 完善音乐播放器 有播放列 ...

  8. iOS音乐播放器相关

    iOS音乐播放器框架主要有两大类:AvPlayer.AvaudioPlayer AvPlayer 能播放本地及网络歌曲 AvaudioPlayer 能播放本地歌曲.有相关代理方法(其实也可以播放网络歌 ...

  9. Ubuntu系统的安装与使用 深度音乐播放器

    1.添加深度音乐播放器的ppa源并更新源缓存 sudo add-apt-repository ppa:noobslab/deepin-sc  sudo apt-get update 2. 安装需要的依 ...

随机推荐

  1. js中兼容性问题的封装(能力检测)

    所谓兼容性,就是看浏览器是否支持当前对象的属性或是方法,先通过获得页面文本内容的方式来了解兼容性,并封装函数或对象以解决此类问题. 获得页面文本内容的方式:innnerText 或textConten ...

  2. SVG图案填充-Pattern

    SVG图案一般用于SVG图形对象的填充fill或描边stroke.这个图形可以是一个SVG元素,也可以是位图图像,通过<pattern>元素在x轴或y轴方向以固定的间隔平铺. <pa ...

  3. Html + Css思维导图

    最近整理的一份Html和Css的思维导图,共享给初学者使用. 各个知识点的详细案例介绍,后期会分阶段依次发布,希望对大家学习html和css有帮助. 如果对文中的知识点有异议,欢迎随时拍砖! 后期也回 ...

  4. 轻松掌握:JavaScript策略模式

    策略模式 定义:定义一系列的算法,把它们一个个封装成函数,也可把它们作为属性统一封装进一个对象,然后再定义一个方法,该方法可根据参数自动选择执行对应的算法. 一般用于在实现一个功能时,有很多个方案可选 ...

  5. Android开发4: Notification编程基础、Broadcast的使用及其静态注册、动态注册方式

    前言 啦啦啦~(博主每次开篇都要卖个萌,大家是不是都厌倦了呢~) 本篇博文希望帮助大家掌握 Broadcast 编程基础,实现动态注册 Broadcast 和静态注册 Broadcast 的方式以及学 ...

  6. Windows系统变量

    %ALLUSERSPROFILE% : 列出所有用户Profile文件位置. %APPDATA% : 列出应用程序数据的默认存放位置. %CD% : 列出当前目录. %CLIENTNAME% : 列出 ...

  7. One-Time Project Recognition

    Please indicate the source if you need to repost. After implementing NetSutie for serveral companies ...

  8. How to get Timer Job History

    1. Get Timer Job internal name with id. Job ID can be found in SharePoint CA. Below PowerShell can h ...

  9. Set up Github Pages with Hexo, migrating from Jekyll

    Set up Github Pages with Hexo, migrating from Jekyll. 本文介绍用Hexo建立github pages, 其中包含了从Jekyll迁移过来的过程. ...

  10. Intent(三)向下一个活动传递数据

    向下传递活动很简单,可以我采用putExtra()方法的重载,把我们想要传递的数据暂时放在intent中,启动活动时从这里取就可以了. 首先我们在MainActivity(主活动)显式声明intent ...