本系列博文,详细讲述一个音乐播放器的实现,以及从网络解析数据获取最新推荐歌曲以及歌曲下载的功能。
功能介绍如下:
1、获取本地歌曲列表,实现歌曲播放功能。
2、利用硬件加速感应器,摇动手机实现切换歌曲的功能
3、利用jsoup解析网页数据,从网络获取歌曲列表,同时实现歌曲和歌词下载到手机本地的功能。
4、通知栏提醒,实现仿QQ音乐播放器的通知栏功能.
涉及的技术有:
1、jsoup解析网络网页,从而获取需要的数据
2、android中访问网络,获取文件到本地的网络请求技术,以及下载文件到本地实现断点下载
3、线程池
4、图片缓存
5、service一直在后台运行
6、手机硬件加速器
7、notification通知栏设计
8、自定义广播
9、android系统文件管理
主要技术是这些,其中,利用jsoup解析网络网页,从而获取需要的数据,请参考我的博文: android中使用JSOUP如何解析网页数据详述

上一篇博文:android-音乐播放器实现及源码下载(一)

有了上一篇博文的准备,现在可以设计主界面,代码如下:

/*** 2015年8月15日 16:34:37* 博文地址:http://blog.csdn.net/u010156024*/
public class MainActivity extends BaseActivity implements OnClickListener {private static final String TAG = MainActivity.class.getSimpleName();private ScrollRelativeLayout mMainContainer;private Indicator mIndicator;private TextView mLocalTextView;private TextView mSearchTextView;private ViewPager mViewPager;private View mPopshownView;private PopupWindow mPopupWindow;private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);registerReceiver();initFragments();setupViews();}/*** 注册广播接收器* 在下载歌曲完成或删除歌曲时,更新歌曲列表*/private void registerReceiver() {IntentFilter filter = new IntentFilter( Intent.ACTION_MEDIA_SCANNER_STARTED);filter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);filter.addDataScheme("file");registerReceiver(mScanSDCardReceiver, filter);}private void setupViews() {mMainContainer = (ScrollRelativeLayout) findViewById(R.id.rl_main_container);mIndicator = (Indicator) findViewById(R.id.main_indicator);mLocalTextView = (TextView) findViewById(R.id.tv_main_local);mSearchTextView = (TextView) findViewById(R.id.tv_main_remote);mViewPager = (ViewPager) findViewById(R.id.vp_main_container);mPopshownView = findViewById(R.id.view_pop_show);mViewPager.setAdapter(mPagerAdapter);mViewPager.setOnPageChangeListener(mPageChangeListener);mLocalTextView.setOnClickListener(this);mSearchTextView.setOnClickListener(this);selectTab(0);}private OnPageChangeListener mPageChangeListener = new OnPageChangeListener() {@Overridepublic void onPageSelected(int position) {selectTab(position);mMainContainer.showIndicator();}@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {mIndicator.scroll(position, positionOffset);}@Overridepublic void onPageScrollStateChanged(int position) {}};private FragmentPagerAdapter mPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {@Overridepublic int getCount() {return mFragments.size();}@Overridepublic Fragment getItem(int position) {return mFragments.get(position);}};/*** 切换导航indicator* @param index*/private void selectTab(int index) {switch (index) {case 0:mLocalTextView.setTextColor(getResources().getColor(R.color.main));mSearchTextView.setTextColor(getResources().getColor(R.color.main_dark));break;case 1:mLocalTextView.setTextColor(getResources().getColor(R.color.main_dark));mSearchTextView.setTextColor(getResources().getColor(R.color.main));break;}}private void initFragments() {LocalFragment localFragment = new LocalFragment();NetSearchFragment netSearchFragment = new NetSearchFragment();mFragments.add(localFragment);mFragments.add(netSearchFragment);}/*** 获取音乐播放服务* @return*/public PlayService getPlayService() {return mPlayService;}public void hideIndicator() {mMainContainer.hideIndicator();}public void showIndicator() {mMainContainer.showIndicator();}public void onPopupWindowShown() {mPopshownView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.layer_show_anim));mPopshownView.setVisibility(View.VISIBLE);}public void onPopupWindowDismiss() {mPopshownView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.layer_gone_anim));mPopshownView.setVisibility(View.GONE);}@Overridepublic void onPublish(int progress) {// 如果当前显示的fragment是音乐列表fragment// 则调用fragment的setProgress设置进度if(mViewPager.getCurrentItem() == 0) {((LocalFragment)mFragments.get(0)).setProgress(progress);}}@Overridepublic void onChange(int position) {// 如果当前显示的fragment是音乐列表fragment// 则调用fragment的setProgress切换歌曲if(mViewPager.getCurrentItem() == 0) {((LocalFragment)mFragments.get(0)).onPlay(position);}}private void onShowMenu() {onPopupWindowShown();if(mPopupWindow == null) {View view = View.inflate(this, R.layout.exit_pop_layout, null);View shutdown = view.findViewById(R.id.tv_pop_shutdown);View exit = view.findViewById(R.id.tv_pop_exit);View cancel = view.findViewById(R.id.tv_pop_cancel);// 不需要共享变量, 所以放这没事shutdown.setOnClickListener(this);exit.setOnClickListener(this);cancel.setOnClickListener(this);mPopupWindow = new PopupWindow(view,LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT, true);mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));mPopupWindow.setAnimationStyle(R.style.popwin_anim);mPopupWindow.setFocusable(true);mPopupWindow.setOnDismissListener(new OnDismissListener() {@Overridepublic void onDismiss() {onPopupWindowDismiss();}});}mPopupWindow.showAtLocation(getWindow().getDecorView(), Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.tv_main_local:mViewPager.setCurrentItem(0);break;case R.id.tv_main_remote:mViewPager.setCurrentItem(1);break;case R.id.tv_pop_exit:stopService(new Intent(this, PlayService.class));stopService(new Intent(this, DownloadService.class));case R.id.tv_pop_shutdown:finish();case R.id.tv_pop_cancel:if(mPopupWindow != null && mPopupWindow.isShowing()) mPopupWindow.dismiss();onPopupWindowDismiss();break;}}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if(keyCode == KeyEvent.KEYCODE_MENU) {onShowMenu();return true;}return super.onKeyDown(keyCode, event);}@Overrideprotected void onDestroy() {unregisterReceiver(mScanSDCardReceiver);super.onDestroy();}private BroadcastReceiver mScanSDCardReceiver = new BroadcastReceiver() {public void onReceive(Context context, Intent intent) {L.l(TAG, "mScanSDCardReceiver---->onReceive()");if(intent.getAction().equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {MusicUtils.initMusicList();((LocalFragment)mFragments.get(0)).onMusicListChanged();}}};
}

主界面主要是两个Fragment,一个是获取本地歌曲列表,亮一个是获取网络歌曲列表。
获取本地歌曲列表LocalFragment代码如下:

/*** 2015年8月15日 16:34:37* 博文地址:http://blog.csdn.net/u010156024*/
public class LocalFragment extends Fragment implements OnClickListener {private ListView mMusicListView;private ImageView mMusicIcon;private TextView mMusicTitle;private TextView mMusicArtist;private ImageView mPreImageView;private ImageView mPlayImageView;private ImageView mNextImageView;private SeekBar mMusicProgress;private MusicListAdapter mMusicListAdapter = new MusicListAdapter();private MainActivity mActivity;private boolean isPause;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setRetainInstance(true);}@Overridepublic void onAttach(Activity activity) {super.onAttach(activity);mActivity = (MainActivity) activity;}@SuppressLint("InflateParams")@Overridepublic View onCreateView(LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {View layout = inflater.inflate(R.layout.local_music_layout, null);setupViews(layout);return layout;}/*** view创建完毕 回调通知activity绑定歌曲播放服务*/@Overridepublic void onStart() {super.onStart();L.l("fragment", "onViewCreated");mActivity.allowBindService();}@Overridepublic void onResume() {super.onResume();isPause = false;}@Overridepublic void onPause() {isPause = true;super.onPause();}/*** stop时, 回调通知activity解除绑定歌曲播放服务*/@Overridepublic void onStop() {super.onStop();L.l("fragment", "onDestroyView");mActivity.allowUnbindService();}private void setupViews(View layout) {mMusicListView = (ListView) layout.findViewById(R.id.lv_music_list);mMusicIcon = (ImageView) layout.findViewById(R.id.iv_play_icon);mMusicTitle = (TextView) layout.findViewById(R.id.tv_play_title);mMusicArtist = (TextView) layout.findViewById(R.id.tv_play_artist);mPreImageView = (ImageView) layout.findViewById(R.id.iv_pre);mPlayImageView = (ImageView) layout.findViewById(R.id.iv_play);mNextImageView = (ImageView) layout.findViewById(R.id.iv_next);mMusicProgress = (SeekBar) layout.findViewById(R.id.play_progress);mMusicListView.setAdapter(mMusicListAdapter);mMusicListView.setOnItemClickListener(mMusicItemClickListener);mMusicListView.setOnItemLongClickListener(mItemLongClickListener);mMusicIcon.setOnClickListener(this);mPreImageView.setOnClickListener(this);mPlayImageView.setOnClickListener(this);mNextImageView.setOnClickListener(this);}private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() {@Overridepublic boolean onItemLongClick(AdapterView<?> parent, View view,int position, long id) {final int pos = position;AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);builder.setTitle("删除该条目");builder.setMessage("确认要删除该条目吗?");builder.setPositiveButton("删除",new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {Music music = MusicUtils.sMusicList.remove(pos);mMusicListAdapter.notifyDataSetChanged();if (new File(music.getUri()).delete()) {scanSDCard();}}});builder.setNegativeButton("取消", null);builder.create().show();return true;}};private OnItemClickListener mMusicItemClickListener = new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position,long id) {play(position);}};/*** 发送广播,通知系统扫描指定的文件* 请参考我的博文:* http://blog.csdn.net/u010156024/article/details/47681851* */private void scanSDCard() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 判断SDK版本是不是4.4或者高于4.4String[] paths = new String[]{Environment.getExternalStorageDirectory().toString()};MediaScannerConnection.scanFile(mActivity, paths, null, null);} else {Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED);intent.setClassName("com.android.providers.media","com.android.providers.media.MediaScannerReceiver");intent.setData(Uri.parse("file://"+ MusicUtils.getMusicDir()));mActivity.sendBroadcast(intent);}}/*** 播放时高亮当前播放条目* 实现播放的歌曲条目可见,且实现指示标记可见* @param position*/private void onItemPlay(int position) {// 将ListView列表滑动到播放的歌曲的位置,是播放的歌曲可见mMusicListView.smoothScrollToPosition(position);// 获取上次播放的歌曲的positionint prePlayingPosition = mMusicListAdapter.getPlayingPosition();// 如果上次播放的位置在可视区域内// 则手动设置invisibleif (prePlayingPosition >= mMusicListView.getFirstVisiblePosition()&& prePlayingPosition <= mMusicListView.getLastVisiblePosition()) {int preItem = prePlayingPosition- mMusicListView.getFirstVisiblePosition();((ViewGroup) mMusicListView.getChildAt(preItem)).getChildAt(0).setVisibility(View.INVISIBLE);}// 设置新的播放位置mMusicListAdapter.setPlayingPosition(position);// 如果新的播放位置不在可视区域// 则直接返回if (mMusicListView.getLastVisiblePosition() < position|| mMusicListView.getFirstVisiblePosition() > position)return;// 如果在可视区域// 手动设置改item visibleint currentItem = position - mMusicListView.getFirstVisiblePosition();((ViewGroup) mMusicListView.getChildAt(currentItem)).getChildAt(0).setVisibility(View.VISIBLE);}/*** 播放音乐item* * @param position*/private void play(int position) {int pos = mActivity.getPlayService().play(position);onPlay(pos);}/*** 播放时,更新控制面板* * @param position*/public void onPlay(int position) {if (MusicUtils.sMusicList.isEmpty() || position < 0)return;//设置进度条的总长度mMusicProgress.setMax(mActivity.getPlayService().getDuration());onItemPlay(position);Music music = MusicUtils.sMusicList.get(position);Bitmap icon = MusicIconLoader.getInstance().load(music.getImage());mMusicIcon.setImageBitmap(icon == null ? ImageTools.scaleBitmap(R.drawable.ic_launcher) : ImageTools.scaleBitmap(icon));mMusicTitle.setText(music.getTitle());mMusicArtist.setText(music.getArtist());if (mActivity.getPlayService().isPlaying()) {mPlayImageView.setImageResource(android.R.drawable.ic_media_pause);} else {mPlayImageView.setImageResource(android.R.drawable.ic_media_play);}//新启动一个线程更新通知栏,防止更新时间过长,导致界面卡顿!new Thread(){@Overridepublic void run() {super.run();mActivity.getPlayService().setRemoteViews();}}.start();}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.iv_play_icon:startActivity(new Intent(mActivity, PlayActivity.class));break;case R.id.iv_play:if (mActivity.getPlayService().isPlaying()) {mActivity.getPlayService().pause(); // 暂停mPlayImageView.setImageResource(android.R.drawable.ic_media_play);} else {onPlay(mActivity.getPlayService().resume()); // 播放}break;case R.id.iv_next:mActivity.getPlayService().next(); // 下一曲break;case R.id.iv_pre:mActivity.getPlayService().pre(); // 上一曲break;}}/*** 设置进度条的进度(SeekBar)* @param progress*/public void setProgress(int progress) {if (isPause)return;mMusicProgress.setProgress(progress);}/*** 主界面MainActivity.java中调用更新歌曲列表*/public void onMusicListChanged() {mMusicListAdapter.notifyDataSetChanged();}
}

获取网络歌曲列表NetSearchFragment代码如下:

/*** 2015年8月15日 16:34:37* 博文地址:http://blog.csdn.net/u010156024*/
public class NetSearchFragment extends Fragmentimplements OnClickListener {protected static final String TAG = NetSearchFragment.class.getSimpleName();private MainActivity mActivity;private LinearLayout mSearchShowLinearLayout;private LinearLayout mSearchLinearLayout;private ImageButton mSearchButton;private EditText mSearchEditText;private ListView mSearchResultListView;private ProgressBar mSearchProgressBar;private TextView mFooterView;private View mPopView;private PopupWindow mPopupWindow;private SearchResultAdapter mSearchResultAdapter;private ArrayList<SearchResult> mResultData = new ArrayList<SearchResult>();private int mPage = 0;private int mLastItem;private boolean hasMoreData = true;/*** 该类是android系统中的下载工具类,非常好用*/private DownloadManager mDownloadManager;private boolean isFirstShown = true;@Overridepublic void onAttach(Activity activity) {super.onAttach(activity);mActivity = (MainActivity) activity;}@SuppressLint("InflateParams")@Overridepublic View onCreateView(LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {View layout = inflater.inflate(R.layout.search_music_layout, null);setupViews(layout);mDownloadManager = (DownloadManager) mActivity.getSystemService(Context.DOWNLOAD_SERVICE);return layout;}/*** 该方法实现的功能是: 当该Fragment不可见时,isVisibleToUser=false* 当该Fragment可见时,isVisibleToUser=true* 该方法由系统调用,重写该方法实现用户可见当前Fragment时再进行数据的加载*/@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);// 当Fragment可见且是第一次加载时if (isVisibleToUser && isFirstShown) {mSearchProgressBar.setVisibility(View.VISIBLE);mSearchResultListView.setVisibility(View.GONE);SongsRecommendation.getInstance().setListener(new SongsRecommendation.OnRecommendationListener() {@Overridepublic void onRecommend(ArrayList<SearchResult> results) {if (results == null || results.isEmpty())return;mSearchProgressBar.setVisibility(View.GONE);mSearchResultListView.setVisibility(View.VISIBLE);mResultData.clear();mResultData.addAll(results);mSearchResultAdapter.notifyDataSetChanged();}}).get();isFirstShown = false;}}private void setupViews(View layout) {mSearchShowLinearLayout = (LinearLayout) layout.findViewById(R.id.ll_search_btn_container);mSearchLinearLayout = (LinearLayout) layout.findViewById(R.id.ll_search_container);mSearchButton = (ImageButton) layout.findViewById(R.id.ib_search_btn);mSearchEditText = (EditText) layout.findViewById(R.id.et_search_content);mSearchResultListView = (ListView) layout.findViewById(R.id.lv_search_result);mSearchProgressBar = (ProgressBar) layout.findViewById(R.id.pb_search_wait);mFooterView = buildFooterView();mSearchShowLinearLayout.setOnClickListener(this);mSearchButton.setOnClickListener(this);mSearchResultListView.addFooterView(mFooterView);mSearchResultAdapter = new SearchResultAdapter(mResultData);mSearchResultListView.setAdapter(mSearchResultAdapter);mSearchResultListView.setOnScrollListener(mListViewScrollListener);mSearchResultListView.setOnItemClickListener(mResultItemClickListener);}private TextView buildFooterView() {TextView footerView = new TextView(mActivity);footerView.setText("加载下一页...");footerView.setGravity(Gravity.CENTER);footerView.setVisibility(View.GONE);return footerView;}/*** 列表中每一列的点击时间监听器*/private OnItemClickListener mResultItemClickListener= new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position,long id) {if (position >= mResultData.size() || position < 0)return;showDownloadDialog(position);}};/*** 底部对话框* @param position*/private void showDownloadDialog(final int position) {mActivity.onPopupWindowShown();if (mPopupWindow == null) {mPopView = View.inflate(mActivity, R.layout.download_pop_layout,null);mPopupWindow = new PopupWindow(mPopView, LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));mPopupWindow.setAnimationStyle(R.style.popwin_anim);mPopupWindow.setFocusable(true);mPopupWindow.setOnDismissListener(new OnDismissListener() {@Overridepublic void onDismiss() {mActivity.onPopupWindowDismiss();}});}//下载按钮点击时间mPopView.findViewById(R.id.tv_pop_download).setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {GetDownloadInfo.getInstance().setListener(mDownloadUrlListener).parse(position,mResultData.get(position).getUrl());dismissDialog();}});mPopView.findViewById(R.id.tv_pop_cancel).setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {dismissDialog();}});/*** 设置对话框展示的位置*/mPopupWindow.showAtLocation(mActivity.getWindow().getDecorView(),Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);}private void dismissDialog() {if (mPopupWindow != null && mPopupWindow.isShowing()) {mPopupWindow.dismiss();}}private OnDownloadGettedListener mDownloadUrlListener =new OnDownloadGettedListener() {@Overridepublic void onMusic(int position, String url) {if (position == -1 || url == null) {Toast.makeText(mActivity, "歌曲链接失效",Toast.LENGTH_SHORT).show();return;}String musicName = mResultData.get(position).getMusicName();mActivity.getDownloadService().download(position,Constants.MUSIC_URL + url, musicName + ".mp3");}@Overridepublic void onLrc(int position, String url) {if (url == null)return;String musicName = mResultData.get(position).getMusicName();DownloadManager.Request request = new DownloadManager.Request(Uri.parse(Constants.MUSIC_URL + url));request.setVisibleInDownloadsUi(false);request.setNotificationVisibility(Request.VISIBILITY_HIDDEN);// request.setShowRunningNotification(false);request.setDestinationUri(Uri.fromFile(new File(MusicUtils.getLrcDir() + musicName + ".lrc")));mDownloadManager.enqueue(request);}};private OnScrollListener mListViewScrollListener =new OnScrollListener() {@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if (mLastItem == mSearchResultAdapter.getCount() && hasMoreData&& scrollState == OnScrollListener.SCROLL_STATE_IDLE) {String searchText = mSearchEditText.getText().toString().trim();if (TextUtils.isEmpty(searchText))return;mFooterView.setVisibility(View.VISIBLE);startSearch(searchText);}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {// 计算可见列表的最后一条的列表是不是最后一个mLastItem = firstVisibleItem + visibleItemCount;}};private void search() {MobileUtils.hideInputMethod(mSearchEditText);String content = mSearchEditText.getText().toString().trim();if (TextUtils.isEmpty(content)) {Toast.makeText(mActivity, "请输入关键词", Toast.LENGTH_SHORT).show();return;}mPage = 0;mSearchProgressBar.setVisibility(View.VISIBLE);mSearchResultListView.setVisibility(View.GONE);startSearch(content);}private void startSearch(String content) {SearchMusic.getInstance().setListener(new SearchMusic.OnSearchResultListener() {@Overridepublic void onSearchResult(ArrayList<SearchResult> results) {if (mPage == 1) {hasMoreData = true;mSearchProgressBar.setVisibility(View.GONE);mSearchResultListView.setVisibility(View.VISIBLE);}mFooterView.setVisibility(View.GONE);if (results == null || results.isEmpty()) {hasMoreData = false;return;}if (mPage == 1)mResultData.clear();mResultData.addAll(results);mSearchResultAdapter.notifyDataSetChanged();}}).search(content, ++mPage);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.ll_search_btn_container:mActivity.hideIndicator();mSearchShowLinearLayout.setVisibility(View.GONE);mSearchLinearLayout.setVisibility(View.VISIBLE);break;case R.id.ib_search_btn:mActivity.showIndicator();mSearchShowLinearLayout.setVisibility(View.VISIBLE);mSearchLinearLayout.setVisibility(View.GONE);search();break;}}
}

上面的两个Fragment主要就是listview,用到了适配器,其中本地歌曲列表适配器代码如下:

/*** 2015年8月15日 16:34:37* 博文地址:http://blog.csdn.net/u010156024* 歌曲列表适配器*/
public class MusicListAdapter extends BaseAdapter {private int mPlayingPosition;public void setPlayingPosition(int position) {mPlayingPosition = position;}public int getPlayingPosition() {return mPlayingPosition;}@Overridepublic int getCount() {return MusicUtils.sMusicList.size();}@Overridepublic Object getItem(int position) {return MusicUtils.sMusicList.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {final ViewHolder holder ;if(convertView == null) {convertView = View.inflate(App.sContext, R.layout.music_list_item, null);holder = new ViewHolder();holder.title = (TextView) convertView.findViewById(R.id.tv_music_list_title);holder.artist = (TextView) convertView.findViewById(R.id.tv_music_list_artist);holder.icon = (ImageView) convertView.findViewById(R.id.music_list_icon);holder.mark = convertView.findViewById(R.id.music_list_selected);convertView.setTag(holder);}else {holder = (ViewHolder) convertView.getTag();}if(mPlayingPosition == position) {holder.mark.setVisibility(View.VISIBLE);}else {holder.mark.setVisibility(View.INVISIBLE);}Bitmap icon = MusicIconLoader.getInstance().load(MusicUtils.sMusicList.get(position).getImage());holder.icon.setImageBitmap(icon == null ? ImageTools.scaleBitmap(R.drawable.ic_launcher) : ImageTools.scaleBitmap(icon));holder.title.setText(MusicUtils.sMusicList.get(position).getTitle());holder.artist.setText(MusicUtils.sMusicList.get(position).getArtist());return convertView;}static class ViewHolder {ImageView icon;TextView title;TextView artist;View mark;}
}

本地列表适配器非常简单,有点要说明的是,其中图片显示部分用到了图片缓存MusicIconLoader类实现,该类代码如下:

/*** 2015年8月15日 16:34:37* 博文地址:http://blog.csdn.net/u010156024*/
public class MusicIconLoader {private static MusicIconLoader sInstance;private LruCache<String, Bitmap> mCache;// 获取MusicIconLoader的实例public synchronized static MusicIconLoader getInstance() {if (sInstance == null)sInstance = new MusicIconLoader();return sInstance;}// 构造方法, 初始化LruCacheprivate MusicIconLoader() {int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);mCache = new LruCache<String, Bitmap>(maxSize) {protected int sizeOf(String key, Bitmap value) {return value.getByteCount();}};}// 根据路径获取图片public Bitmap load(final String uri) {if (uri == null)return null;final String key = Encrypt.md5(uri);Bitmap bmp = getFromCache(key);if (bmp != null)return bmp;bmp = BitmapFactory.decodeFile(uri);addToCache(key, bmp);return bmp;}// 从内存中获取图片private Bitmap getFromCache(final String key) {return mCache.get(key);}// 将图片缓存到内存中private void addToCache(final String key, final Bitmap bmp) {if (getFromCache(key) == null && key != null && bmp != null)mCache.put(key, bmp);}
}

该类也比较简单,主要使用android.support.v4.util.LruCache;类来实现图片的缓存,首先图片从缓存中获取,如果没有再重新加载。
同时,本地歌曲列表使用到了MusicUtils类来获取本地歌曲列表,代码如下:

/*** 2015年8月15日 16:34:37* 博文地址:http://blog.csdn.net/u010156024*/
public class MusicUtils {// 存放歌曲列表public static ArrayList<Music> sMusicList = new ArrayList<Music>();public static void initMusicList() {// 获取歌曲列表sMusicList.clear();sMusicList.addAll(LocalMusicUtils.queryMusic(getBaseDir()));}/*** 获取内存卡根* @return*/public static String getBaseDir() {String dir = null;if (!Environment.getExternalStorageState().equals(Environment.MEDIA_UNMOUNTED)) {dir = Environment.getExternalStorageDirectory() + File.separator;} else {dir = App.sContext.getFilesDir() + File.separator;}return dir;}/*** 获取应用程序使用的本地目录* @return*/public static String getAppLocalDir() {String dir = null;if (!Environment.getExternalStorageState().equals(Environment.MEDIA_UNMOUNTED)) {dir = Environment.getExternalStorageDirectory() + File.separator+ "liteplayer" + File.separator;} else {dir = App.sContext.getFilesDir() + File.separator + "liteplayer" + File.separator;}return mkdir(dir);}/*** 获取音乐存放目录* @return*/public static String getMusicDir() {String musicDir = getAppLocalDir() + "music" + File.separator;return mkdir(musicDir);}/*** 获取歌词存放目录* * @return*/public static String getLrcDir() {String lrcDir = getAppLocalDir() + "lrc" + File.separator;return mkdir(lrcDir);}/*** 创建文件夹* @param dir* @return*/public static String mkdir(String dir) {File f = new File(dir);if (!f.exists()) {for (int i = 0; i < 5; i++) {if(f.mkdirs()) return dir;}return null;}return dir;}
}

其中真正执行本地歌曲列表获取的方法是:

public static void initMusicList() {// 获取歌曲列表sMusicList.clear();sMusicList.addAll(LocalMusicUtils.queryMusic(getBaseDir()));}

其中的LocalMusicUtils类如下:

/*** 2015年8月15日 16:34:37* 博文地址:http://blog.csdn.net/u010156024*/
public class LocalMusicUtils {/*** 根据id获取歌曲uri* @deprecated* @param musicId* @return*/public static String queryMusicById(int musicId) {String result = null;Cursor cursor = App.sContext.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,new String[] { MediaStore.Audio.Media.DATA },MediaStore.Audio.Media._ID + "=?",new String[] { String.valueOf(musicId) }, null);for (cursor.moveToFirst(); !cursor.isAfterLast();) {result = cursor.getString(0);break;}cursor.close();return result;}/*** 获取目录下的歌曲* @param dirName*/public static ArrayList<Music> queryMusic(String dirName) {ArrayList<Music> results = new ArrayList<Music>();Cursor cursor = App.sContext.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,MediaStore.Audio.Media.DATA + " like ?",new String[] { dirName + "%" },MediaStore.Audio.Media.DEFAULT_SORT_ORDER);if(cursor == null) return results;// id title singer data time imageMusic music;for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {// 如果不是音乐String isMusic = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.IS_MUSIC));if (isMusic != null && isMusic.equals("")) continue;String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));String artist = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));if(isRepeat(title, artist)) continue;music = new Music();music.setId(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)));music.setTitle(title);music.setArtist(artist);music.setUri(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)));music.setLength(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)));music.setImage(getAlbumImage(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID))));results.add(music);}cursor.close();return results;}/*** 根据音乐名称和艺术家来判断是否重复包含了* @param title* @param artist* @return*/private static boolean isRepeat(String title, String artist) {for(Music music : MusicUtils.sMusicList) {if(title.equals(music.getTitle()) && artist.equals(music.getArtist())) {return true;}}return false;}/*** 根据歌曲id获取图片* @param albumId* @return*/private static String getAlbumImage(int albumId) {String result = "";Cursor cursor = null;try {cursor = App.sContext.getContentResolver().query(Uri.parse("content://media/external/audio/albums/"+ albumId), new String[] { "album_art" }, null,null, null);for (cursor.moveToFirst(); !cursor.isAfterLast();) {result = cursor.getString(0);break;}} catch (Exception e) {e.printStackTrace();} finally {if (null != cursor) {cursor.close();}}return null == result ? null : result;}
}

至此本地歌曲列表Fragment讲述完毕,说白了就是通过LocalMusicUtils工具类,获取本地歌曲列表,填装进适配器,然后设置LocalFragment完成UI。

网络歌曲列表的适配器,SearchResultAdapter类实现,非常简单,直接加载数据就可以了。关于jsoup解析网页数据部分,请参考我的博文:android中使用JSOUP如何解析网页数据详述
该适配器代码如下:

/*** 2015年8月15日 16:34:37* 博文地址:http://blog.csdn.net/u010156024*/
public class SearchResultAdapter extends BaseAdapter {private ArrayList<SearchResult> mSearchResult;public SearchResultAdapter(ArrayList<SearchResult> searchResult) {mSearchResult = searchResult;}@Overridepublic int getCount() {return mSearchResult.size();}@Overridepublic Object getItem(int position) {return mSearchResult.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;if(convertView == null) {convertView = View.inflate(App.sContext, R.layout.search_result_item, null);holder = new ViewHolder();holder.title = (TextView) convertView.findViewById(R.id.tv_search_result_title);holder.artist = (TextView) convertView.findViewById(R.id.tv_search_result_artist);holder.album = (TextView) convertView.findViewById(R.id.tv_search_result_album);convertView.setTag(holder);}else {holder = (ViewHolder) convertView.getTag();}String artist = mSearchResult.get(position).getArtist();String album = mSearchResult.get(position).getAlbum();holder.title.setText(mSearchResult.get(position).getMusicName());if(!TextUtils.isEmpty(artist)) holder.artist.setText(artist);else holder.artist.setText("未知艺术家");if(!TextUtils.isEmpty(album)) holder.album.setText(album);else holder.album.setText("未知专辑");return convertView;}static class ViewHolder {public TextView title;public TextView artist;public TextView album;}
}

非常简单的两个适配器代码。

至此 主界面设计完成,下一篇博文,详细讲述两个service服务的设计。

音乐播放器源码下载


PS:
代码已更新,本地手机没有MP3文件的话,也不会出现崩溃。

android-音乐播放器实现及源码下载(二)相关推荐

  1. java计算机毕业设计vue开发一个简单音乐播放器(附源码、数据库)

    java计算机毕业设计vue开发一个简单音乐播放器(附源码.数据库) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也行)+ Ec ...

  2. H5音乐播放器(包含源码与示例)

    H5音乐播放器(包含源码与示例) 基于Angular+ionic的H5音乐播放器,源码:https://gitee.com/CrimsonHu/h5-music-player 示例地址 建议使用原版c ...

  3. 基于JAVAvue开发一个简单音乐播放器计算机毕业设计源码+数据库+lw文档+系统+部署

    基于JAVAvue开发一个简单音乐播放器计算机毕业设计源码+数据库+lw文档+系统+部署 基于JAVAvue开发一个简单音乐播放器计算机毕业设计源码+数据库+lw文档+系统+部署 本源码技术栈: 项目 ...

  4. 基于QT开发的音乐播放器(附源码)

    基于QT开发的音乐播放器(附源码) 一.简介 1.介绍 2.功能描述 3.系统功能层次模块图 4.各模块功能描述 (1)播放界面 (2)歌词 (3)歌曲信息 (4)歌曲列表 5.文件格式 6.运行环境 ...

  5. android-音乐播放器实现及源码下载(四)

    本系列博文,详细讲述一个音乐播放器的实现,以及从网络解析数据获取最新推荐歌曲以及歌曲下载的功能. 功能介绍如下: 1.获取本地歌曲列表,实现歌曲播放功能. 2.利用硬件加速感应器,摇动手机实现切换歌曲 ...

  6. HTML5 可视化音乐播放器(附源码)

    文章目录 一.前言 二.主要功能 三.效果图 四.主要介绍 1.关于原创/来源 2.关于JS原生版 3.关于Layui+jQuery版 五.结语 一.前言 最近某音乐播放器越来越迷,以前下载的本地音乐 ...

  7. 计算机毕业设计android的在线音乐播放器app设计(源码+系统+mysql数据库+Lw文档)

    项目介绍 Android是Google公司公布的基于Linux内核的手机操作系统,其代码属于完全开放,为开源软件开发人员提供使用方便的框架和平台.,本文以Android开发平台为基础,介绍了音乐播放器 ...

  8. 音乐播放器(附源码)

    音乐播放器 刚学编程的时候写了一个音乐播放器,博客中不会粘贴源码,需要的请到我的github下载https://github.com/wangffei/music_player 下面是网站的演示地址, ...

  9. Vue2仿网易云风格音乐播放器(附源码)

    Vue2仿网易云风格音乐播放器 1.整体效果 2.使用技术 3.实现内容 4.源码 5.使用图片 1.整体效果 2.使用技术 使用了HTML5 + CSS3进行页面布局及美化 使用Vue2进行数据渲染 ...

最新文章

  1. 2019.03.10----LINUX学习笔记
  2. python怎么捕获mysql报错
  3. 牛客16502 螺旋矩阵
  4. Docker简介与简单使用 | 技术头条
  5. 全球链界科技发展大会_科技界女性占五席
  6. DNS原理及其解析过程 精彩剖析
  7. 10进制转16进制 java_Java中将10进制转换成16进制
  8. 手机客户端应用功能测试方法总结
  9. ROST情感分析的语法规则_从词法分析角度聊 Go 代码组成
  10. Layout state should be one of 100 but it is 10起因和解决
  11. Docker安装以及docker run hello-world 不能下载镜像报错
  12. Linux中编译mdio命令,Linux 下smi/mdio总线通信
  13. python 3d重建_python三维重建
  14. java画乌龟_简单的实现java多线程——龟兔赛跑
  15. RFE -- 用户活跃度模型
  16. 使用枚举实现英文转盲文
  17. 最佳会员WooCommerce插件比较
  18. 武忠祥老师每日一题||不定积分基础训练(六)
  19. mysql hacing_manjaro 折腾日记
  20. 林语堂:我生之初尚无为

热门文章

  1. 【日记】Java学习日记(第63天)持续无聊更新
  2. 心电图 python_【铎悦干货】解析心电图基础(二),看完绝不后悔
  3. 用 VB 打开任意盘(硬盘/U盘/光盘)的文件.
  4. 意法半导体透露赛米控碳化硅合作细节
  5. 多节点靶场(域渗透)
  6. 【计算机毕业设计】智乐健身后台管理系统
  7. Redis 源码该怎么读?(译文)
  8. 自动化运维工具-提升运维舒适度
  9. 网鼎杯玄武组部分web题解
  10. vue中动态请求URL乱码