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. 元首的愤怒 SharePoint Apps

    柏林数据中心的服务器机架已经插满.CPU 100%.电力基础设施处在崩溃的边缘,但当元首决定迁移到 Office 365 的时候,将军们却告诉他那里没有 Farm Solution,5 年多的投资将付 ...

  2. SQL Queries from Transactional Plugin Pipeline

    Sometimes the LINQ, Query Expressions or Fetch just doesn't give you the ability to quickly query yo ...

  3. AndRodi Strudio中的按钮时件

    AndRodi Studio中的按钮时件注册一定要写在onCraete中 @Override protected void onCreate(Bundle savedInstanceState) { ...

  4. php设计模式 原型模式

    原型模式与工程模式作用类似,都是用来创建对象. 与工程模式的实现不同,原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象.这样就免去了类创建时重复的初始化操作. 原型模式适用于大 ...

  5. 常用的HTTP状态代码

    常用的HTTP状态代码如下: 400 无法解析此请求. 401.1 未经授权:访问由于凭据无效被拒绝. 401.2 未经授权: 访问由于服务器配置倾向使用替代身份验证方法而被拒绝. 401.3 未经授 ...

  6. Oracle索引梳理系列(六)- Oracle索引种类之函数索引

    版权声明:本文发布于http://www.cnblogs.com/yumiko/,版权由Yumiko_sunny所有,欢迎转载.转载时,请在文章明显位置注明原文链接.若在未经作者同意的情况下,将本文内 ...

  7. sql server 里面的 dynamic Data Masking

    有时候啊,当我们存放在数据库里面的数据,需要明文存放,但是对于前台查询出来的,又不太希望明文放出去的时候(比方说客户的手机啊,邮箱啊)之类有点敏感的信息,之前通常是保存个明文,然后在前台展现的时候再特 ...

  8. 机器学习之寻找KMeans的最优K

    K-Means聚类算法是最为经典的,同时也是使用最为广泛的一种基于划分的聚类算法,它属于基于距离的无监督聚类算法.KMeans算法简单实用,在机器学习算法中占有重要的地位.对于KMeans算法而言,如 ...

  9. Linux下通配符总结

    * - 通配符,代表任意字符(0到多个)? - 通配符,代表一个字符# - 注释/ - 跳转符号,将特殊字符或通配符还原成一般符号| - 分隔两个管线命令的界定; - 连续性命令的界定~ - 用户的根 ...

  10. C# 自定义文件图标 双击启动 (修改注册表)

    程序生成的自定义文件,比如后缀是.test 这种文件怎么直接启动打开程序,并打开本文件呢 1.双击打开 2.自定义的文件,有图标显示 3.自定义的文件,点击右键有相应的属性 后台代码:(如何在注册表中 ...