admin管理员组

文章数量:1572325

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

之前的三篇博文的链接:
android-音乐播放器实现及源码下载(一)讲述了activity基类实现和application类的实现,以及最终设计界面展示。
android-音乐播放器实现及源码下载(二)讲述了主界面的设计和实现
android-音乐播放器实现及源码下载(三)讲述了两个service服务的设计和实现

本篇博文讲述播放界面的设计和实现,以及做最后的总结。

这个是播放界面的截图,仿QQ音乐播放界面的实现,代码如下:

/**
 * 2015年8月15日 16:34:37
 *  博文地址:http://blog.csdn/u010156024
 */
public class PlayActivity extends BaseActivity implements OnClickListener {

	private LinearLayout mPlayContainer;
	private ImageView mPlayBackImageView; // back button
	private TextView mMusicTitle; // music title
	private ViewPager mViewPager; // cd or lrc
	private CDView mCdView; // cd
	private SeekBar mPlaySeekBar; // seekbar
	private ImageButton mStartPlayButton; // start or pause
	private TextView mSingerTextView; // singer
	private LrcView mLrcViewOnFirstPage; // single line lrc
	private LrcView mLrcViewOnSecondPage; // 7 lines lrc
	private PagerIndicator mPagerIndicator; // indicator

	// cd view and lrc view
	private ArrayList<View> mViewPagerContent = new ArrayList<View>(2);

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.play_activity_layout);
		setupViews();
	}

	/**
	 * 初始化view
	 */
	private void setupViews() {
		mPlayContainer = (LinearLayout) findViewById(R.id.ll_play_container);
		mPlayBackImageView = (ImageView) findViewById(R.id.iv_play_back);
		mMusicTitle = (TextView) findViewById(R.id.tv_music_title);
		mViewPager = (ViewPager) findViewById(R.id.vp_play_container);
		mPlaySeekBar = (SeekBar) findViewById(R.id.sb_play_progress);
		mStartPlayButton = (ImageButton) findViewById(R.id.ib_play_start);
		mPagerIndicator = (PagerIndicator) findViewById(R.id.pi_play_indicator);

		// 动态设置seekbar的margin
		MarginLayoutParams p = (MarginLayoutParams) mPlaySeekBar
				.getLayoutParams();
		p.leftMargin = (int) (App.sScreenWidth * 0.1);
		p.rightMargin = (int) (App.sScreenWidth * 0.1);

		mPlaySeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);

		initViewPagerContent();
		// 设置viewpager的切换动画
		mViewPager.setPageTransformer(true, new PlayPageTransformer());
		mPagerIndicator.create(mViewPagerContent.size());
		mViewPager.setOnPageChangeListener(mPageChangeListener);
		mViewPager.setAdapter(mPagerAdapter);

		mPlayBackImageView.setOnClickListener(this);
	}

	@Override
	protected void onResume() {
		super.onResume();
		allowBindService();
	}

	@Override
	protected void onPause() {
		allowUnbindService();
		super.onPause();
	}

	private OnPageChangeListener mPageChangeListener =
			new OnPageChangeListener() {
		@Override
		public void onPageSelected(int position) {
			if (position == 0) {
				if (mPlayService.isPlaying())
					mCdView.start();
			} else {
				mCdView.pause();
			}
			mPagerIndicator.current(position);
		}

		@Override
		public void onPageScrolled(int arg0, float arg1, int arg2) {
		}

		@Override
		public void onPageScrollStateChanged(int arg0) {
		}
	};

	/**
	 * 拖动进度条
	 */
	private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
			new SeekBar.OnSeekBarChangeListener() {
		@Override
		public void onProgressChanged(SeekBar seekBar, int progress,
				boolean fromUser) {

		}

		@Override
		public void onStartTrackingTouch(SeekBar seekBar) {

		}

		@Override
		public void onStopTrackingTouch(SeekBar seekBar) {
			int progress = seekBar.getProgress();
			mPlayService.seek(progress);
			mLrcViewOnFirstPage.onDrag(progress);
			mLrcViewOnSecondPage.onDrag(progress);
		}
	};

	private PagerAdapter mPagerAdapter = new PagerAdapter() {
		@Override
		public int getCount() {
			return mViewPagerContent.size();
		}

		@Override
		public boolean isViewFromObject(View view, Object obj) {
			return view == obj;
		}

		/**
		 * 该方法是PagerAdapter的预加载方法,系统调用 当显示第一个界面时,
		 * 第二个界面已经预加载,此时调用的就是该方法。
		 */
		@Override
		public Object instantiateItem(ViewGroup container, int position) {
			container.addView(mViewPagerContent.get(position));
			return mViewPagerContent.get(position);
		}

		@Override
		public void destroyItem(ViewGroup container, int position, Object object) {
			((ViewPager) container).removeView((View) object);
		}
	};

	/**
	 * 初始化viewpager的内容
	 */
	private void initViewPagerContent() {
		View cd = View.inflate(this, R.layout.play_pager_item_1, null);
		mCdView = (CDView) cd.findViewById(R.id.play_cdview);
		mSingerTextView = (TextView) cd.findViewById(R.id.play_singer);
		mLrcViewOnFirstPage = (LrcView) cd.findViewById(R.id.play_first_lrc);

		View lrcView = View.inflate(this, R.layout.play_pager_item_2, null);
		mLrcViewOnSecondPage = (LrcView) lrcView
				.findViewById(R.id.play_first_lrc_2);

		mViewPagerContent.add(cd);
		mViewPagerContent.add(lrcView);
	}

	@SuppressWarnings("deprecation")
	private void setBackground(int position) {
		Music currentMusic = MusicUtils.sMusicList.get(position);
		Bitmap bgBitmap = MusicIconLoader.getInstance().load(
				currentMusic.getImage());
		if (bgBitmap == null) {
			bgBitmap = BitmapFactory.decodeResource(getResources(),
					R.drawable.ic_launcher);
		}
		mPlayContainer.setBackgroundDrawable(
				new ShapeDrawable(new PlayBgShape(bgBitmap)));
	}

	/**
	 * 上一曲
	 * 
	 * @param view
	 */
	public void pre(View view) {
		mPlayService.pre(); // 上一曲
	}

	/**
	 * 播放 or 暂停
	 * 
	 * @param view
	 */
	public void play(View view) {
		if (mPlayService.isPlaying()) {
			mPlayService.pause(); // 暂停
			mCdView.pause();
			mStartPlayButton
					.setImageResource(R.drawable.player_btn_play_normal);
		} else {
			onPlay(mPlayService.resume()); // 播放
		}
	}

	/**
	 * 上一曲
	 * 
	 * @param view
	 */
	public void next(View view) {
		mPlayService.next(); // 上一曲
	}

	/**
	 * 播放时调用 主要设置显示当前播放音乐的信息
	 * 
	 * @param position
	 */
	private void onPlay(int position) {
		Music music = MusicUtils.sMusicList.get(position);

		mMusicTitle.setText(music.getTitle());
		mSingerTextView.setText(music.getArtist());
		mPlaySeekBar.setMax(music.getLength());
		Bitmap bmp = MusicIconLoader.getInstance().load(music.getImage());
		if (bmp == null)
			bmp = BitmapFactory.decodeResource(getResources(),
					R.drawable.ic_launcher);
		mCdView.setImage(ImageTools.scaleBitmap(bmp,
				(int) (App.sScreenWidth * 0.8)));

		if (mPlayService.isPlaying()) {
			mCdView.start();
			mStartPlayButton
					.setImageResource(R.drawable.player_btn_pause_normal);
		} else {
			mCdView.pause();
			mStartPlayButton
					.setImageResource(R.drawable.player_btn_play_normal);
		}
	}

	private void setLrc(int position) {
		Music music = MusicUtils.sMusicList.get(position);
		String lrcPath = MusicUtils.getLrcDir() + music.getTitle() + ".lrc";
		mLrcViewOnFirstPage.setLrcPath(lrcPath);
		mLrcViewOnSecondPage.setLrcPath(lrcPath);
	}

	@Override
	public void onPublish(int progress) {
		mPlaySeekBar.setProgress(progress);
		if (mLrcViewOnFirstPage.hasLrc())
			mLrcViewOnFirstPage.changeCurrent(progress);
		if (mLrcViewOnSecondPage.hasLrc())
			mLrcViewOnSecondPage.changeCurrent(progress);
	}

	@Override
	public void onChange(int position) {
		setBackground(position);
		onPlay(position);
		setLrc(position);
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.iv_play_back:
			finish();
			break;
		default:
			break;
		}
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
	}
}

整个代码比较简单,需要特别说明的是,播放界面并没有和MediaPlayer类耦合,而是和PlayService耦合,对于歌曲的播放,暂停,下一曲、上一曲等操作都和PlayService进行交互,这样做好处非常明显,整个项目中所有对播放歌曲的操作都通过PlayService进行,同时,通过PlayService可以更新通知栏信息。

**

总结

**
一、
项目中,用到了比较多的回调接口,回调接口确实非常好用,解决之间的耦合关系。
service类通过回调接口OnMusicEventListener中的方法,调用activity类中的

public void onPublish(int percent);
public void onChange(int position);

两个方法来实现UI的更新。
而Activity类中通过基类BaseActivity中的以下代码

private ServiceConnection mPlayServiceConnection = new ServiceConnection() {
		@Override
		public void onServiceDisconnected(ComponentName name) {
			L.l(TAG, "play--->onServiceDisconnected");
			mPlayService = null;
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			mPlayService = ((PlayService.PlayBinder) service).getService();
			mPlayService.setOnMusicEventListener(mMusicEventListener);
			onChange(mPlayService.getPlayingPosition());
		}
	};
	
	private ServiceConnection mDownloadServiceConnection = new ServiceConnection() {
		@Override
		public void onServiceDisconnected(ComponentName name) {
			L.l(TAG, "download--->onServiceDisconnected");
			mDownloadService = null;
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			mDownloadService = ((DownloadService.DownloadBinder) service).getService();
		}
	};
	
	/**
	 * 音乐播放服务回调接口的实现类
	 */
	private PlayService.OnMusicEventListener mMusicEventListener = 
			new PlayService.OnMusicEventListener() {
		@Override
		public void onPublish(int progress) {
			BaseActivity.this.onPublish(progress);
		}

		@Override
		public void onChange(int position) {
			BaseActivity.this.onChange(position);
		}
	};

完成与两个service服务的绑定,实现与service服务的交互。

以上是两个非常关键的方法实现两者之间的交互,应该说是非常好的处理两者之间的信息传递。
其中在获取网络歌曲列表的过程中,也是用到了回调接口的方法进行数据传递,SongsRecommendation类中的

/**
	 * 回调接口 获取数据之后,通过该接口设置数据传递
	 */
	public interface OnRecommendationListener {
		public void onRecommend(ArrayList<SearchResult> results);
	}

这个接口就是进行结果传递的回调接口。所以大家在看代码的过程中,务必了解并看懂回调接口到底是如何进行的。
二、

项目中使用了比较多的线程池问题,大家务必了解线程池的用法。其实线程池用法非常简单。

private ExecutorService mThreadPool;
mThreadPool = Executors.newSingleThreadExecutor();
mThreadPool.execute(new Runnable() {
			@Override
			public void run() {
				ArrayList<SearchResult> result = getMusicList();
				if (result == null) {
					mHandler.sendEmptyMessage(Constants.FAILED);
					return;
				}
				mHandler.obtainMessage(Constants.SUCCESS, result)
						.sendToTarget();
			}
		});

以上便是线程池的基本用法了。easy!!

三、
jsoup是本项目中的重点内容,请参考我的博文:android中使用JSOUP如何解析网页数据详述

四、
本项目中的两个service服务也是关键内容,关于service服务一直在后台运行的内容,请参考我的博文:实现音乐播放器后台Service服务一直存在的解决思路

以上四点是项目中比较重要的部分,博文写的比较仓促,难免有什么不好的地方,如果大家有什么疑问或问题,欢迎给我留言,我一定尽快回复大家!_【握手】

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

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

本文标签: 播放器源码下载音乐android