本系列博文,详细讲述一个音乐播放器的实现,以及从网络解析数据获取最新推荐歌曲以及歌曲下载的功能。
功能介绍如下:
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 mFragments = new ArrayList();
@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 mResultData = new ArrayList();
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 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 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 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(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 sMusicList = new ArrayList();
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 queryMusic(String dirName) {
ArrayList results = new ArrayList();
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 mSearchResult;
public SearchResultAdapter(ArrayList 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服务的设计。
音乐播放器源码下载
PS:
代码已更新,本地手机没有MP3文件的话,也不会出现崩溃。