android-音乐播放器实现及源码下载(二)
2015-08-16 11:48
681 查看
本系列博文,详细讲述一个音乐播放器的实现,以及从网络解析数据获取最新推荐歌曲以及歌曲下载的功能。
功能介绍如下:
1、获取本地歌曲列表,实现歌曲播放功能。
2、利用硬件加速感应器,摇动手机实现切换歌曲的功能
3、利用jsoup解析网页数据,从网络获取歌曲列表,同时实现歌曲和歌词下载到手机本地的功能。
4、通知栏提醒,实现仿QQ音乐播放器的通知栏功能.
涉及的技术有:
1、jsoup解析网络网页,从而获取需要的数据
2、android中访问网络,获取文件到本地的网络请求技术,以及下载文件到本地实现断点下载
3、线程池
4、图片缓存
5、service一直在后台运行
6、手机硬件加速器
7、notification通知栏设计
8、自定义广播
9、android系统文件管理
主要技术是这些,其中,利用jsoup解析网络网页,从而获取需要的数据,请参考我的博文: android中使用JSOUP如何解析网页数据详述
上一篇博文:android-音乐播放器实现及源码下载(一)
有了上一篇博文的准备,现在可以设计主界面,代码如下:
主界面主要是两个Fragment,一个是获取本地歌曲列表,亮一个是获取网络歌曲列表。
获取本地歌曲列表LocalFragment代码如下:
获取网络歌曲列表NetSearchFragment代码如下:
上面的两个Fragment主要就是listview,用到了适配器,其中本地歌曲列表适配器代码如下:
本地列表适配器非常简单,有点要说明的是,其中图片显示部分用到了图片缓存MusicIconLoader类实现,该类代码如下:
该类也比较简单,主要使用android.support.v4.util.LruCache;类来实现图片的缓存,首先图片从缓存中获取,如果没有再重新加载。
同时,本地歌曲列表使用到了MusicUtils类来获取本地歌曲列表,代码如下:
其中真正执行本地歌曲列表获取的方法是:
其中的LocalMusicUtils类如下:
至此本地歌曲列表Fragment讲述完毕,说白了就是通过LocalMusicUtils工具类,获取本地歌曲列表,填装进适配器,然后设置LocalFragment完成UI。
网络歌曲列表的适配器,SearchResultAdapter类实现,非常简单,直接加载数据就可以了。关于jsoup解析网页数据部分,请参考我的博文:android中使用JSOUP如何解析网页数据详述
该适配器代码如下:
非常简单的两个适配器代码。
至此 主界面设计完成,下一篇博文,详细讲述两个service服务的设计。
音乐播放器源码下载
功能介绍如下:
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>(); @Override protected 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() { @Override public void onPageSelected(int position) { selectTab(position); mMainContainer.showIndicator(); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { mIndicator.scroll(position, positionOffset); } @Override public void onPageScrollStateChanged(int position) { } }; private FragmentPagerAdapter mPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public int getCount() { return mFragments.size(); } @Override public 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); } @Override public void onPublish(int progress) { // 如果当前显示的fragment是音乐列表fragment // 则调用fragment的setProgress设置进度 if(mViewPager.getCurrentItem() == 0) { ((LocalFragment)mFragments.get(0)).setProgress(progress); } } @Override public 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() { @Override public void onDismiss() { onPopupWindowDismiss(); } }); } mPopupWindow.showAtLocation(getWindow().getDecorView(), Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0); } @Override public 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; } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_MENU) { onShowMenu(); return true; } return super.onKeyDown(keyCode, event); } @Override protected 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; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } @Override public void onAttach(Activity activity) { super.onAttach(activity); mActivity = (MainActivity) activity; } @SuppressLint("InflateParams") @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View layout = inflater.inflate(R.layout.local_music_layout, null); setupViews(layout); return layout; } /** * view创建完毕 回调通知activity绑定歌曲播放服务 */ @Override public void onStart() { super.onStart(); L.l("fragment", "onViewCreated"); mActivity.allowBindService(); } @Override public void onResume() { super.onResume(); isPause = false; } @Override public void onPause() { isPause = true; super.onPause(); } /** * stop时, 回调通知activity解除绑定歌曲播放服务 */ @Override public 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() { @Override public 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() { @Override public 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.4 String[] 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); // 获取上次播放的歌曲的position int prePlayingPosition = mMusicListAdapter.getPlayingPosition(); // 如果上次播放的位置在可视区域内 // 则手动设置invisible if (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 visible int 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(){ @Override public void run() { super.run(); mActivity.getPlayService().setRemoteViews(); } }.start(); } @Override public 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 Fragment implements 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; @Override public void onAttach(Activity activity) { super.onAttach(activity); mActivity = (MainActivity) activity; } @SuppressLint("InflateParams") @Override public 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时再进行数据的加载 */ @Override public 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() { @Override public 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() { @Override public 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() { @Override public void onDismiss() { mActivity.onPopupWindowDismiss(); } }); } //下载按钮点击时间 mPopView.findViewById(R.id.tv_pop_download).setOnClickListener( new OnClickListener() { @Override public 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() { @Override public 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() { @Override public 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"); } @Override public 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() { @Override public 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); } } @Override public 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() { @Override public 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); } @Override public 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; } @Override public int getCount() { return MusicUtils.sMusicList.size(); } @Override public Object getItem(int position) { return MusicUtils.sMusicList.get(position); } @Override public long getItemId(int position) { return position; } @Override public 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; } // 构造方法, 初始化LruCache private 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 image Music 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; } @Override public int getCount() { return mSearchResult.size(); } @Override public Object getItem(int position) { return mSearchResult.get(position); } @Override public long getItemId(int position) { return position; } @Override public 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服务的设计。
音乐播放器源码下载
相关文章推荐
- android-音乐播放器实现及源码下载(一)
- android体系结构以及源代码阅读环境搭建
- Android进程间通信模型之思考Binder和Service_manager的模型
- Android_Animation Drawable(旋转动画)
- Android EventBus源码解析 带你深入理解EventBus
- Android启动页
- Android ADB工具-管理设备/取设备硬件信息(一)
- Android之assets资源
- 我开发的第一个Android软件
- Android Resources
- Android底层原理之从Service_manager源码分析Android进程间通信过程
- 记录下帮助一位网友解决的关于android子控件的onTouch或onClick和父OnTouch 冲突的问题。
- Android(java)学习笔记164:Relativelayout相对布局案例
- android环境配置
- Android onPause 和onSaveInstanceState
- android圆形旋转菜单,而对于移动转换功能支持
- ADT导出Android工程到Androidstudio
- android跨进程通讯一:android中跨进程通讯的4种方式
- Android studio百度地图(一)
- 关于掌纹识别的android版本开发(实例调用JNI)