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

本系列博文,详细讲述一个音乐播放器的实现,以及从网络解析数据获取最新推荐歌曲以及歌曲下载的功能。
功能介绍如下:
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服务的设计。

想要源代码的同学,给我发邮件吧(nyyin@qq.com)


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

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值