本系列博文,详细讲述一个音乐播放器的实现,以及从网络解析数据获取最新推荐歌曲以及歌曲下载的功能。
功能介绍如下:
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.net/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服务一直存在的解决思路
以上四点是项目中比较重要的部分,博文写的比较仓促,难免有什么不好的地方,如果大家有什么疑问或问题,欢迎给我留言,我一定尽快回复大家!^_^【握手】
音乐播放器源码下载
版权声明:本文为博主原创文章,未经博主允许不得转载。