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 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文件的话,也不会出现崩溃。

你可能感兴趣的:(android开发)