Music源碼分析(2):歌手界面ArtistAlbumBrowserActivity

前言:ArtistAlbumBrowserActivity繼承自ExpandableListActivity,ExpandableListActivity是封裝了ExpandableListView的Activity,ExpandableListView是可以展開的ListView;在ArtistAlbumBrowserActivity中,一級界面的group是顯示歌手名字的ListView,點擊歌手名字展開之後,出現該歌手的專輯列表;

 

1.首先看下ExpandableListActivity的聲明:

public class ExpandableListActivity extends Activity implements
        OnCreateContextMenuListener,
        ExpandableListView.OnChildClickListener, ExpandableListView.OnGroupCollapseListener,
        ExpandableListView.OnGroupExpandListener { 
................................
}

點評:在這個類的聲明中,我們看到ExpandableListActivity實現了4個監聽,分別是:

①OnCreateContextMenuListener

長按一個view,彈出view的上下文菜單

②ExpandableListView.OnChildClickListener

點擊二級界面listview item時的監聽

③ExpandableListView.OnGroupCollapseListener

點擊一級界面listview item收縮時的監聽

④ExpandableListView.OnGroupExpandListener

點擊一級界面listview item展開時的監聽

ArtistAlbumBrowserActivity重寫了onCreateContextMenu以及OnChildClick()方法;

 

2.開始分析ArtistAlbumBrowserActivity;

先從onCreate()方法開始:

public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    MusicLogUtils.d(TAG, "onCreate");
    //窗口自帶進度條,這個進度條是音樂播放進度條,與主界面nowplaying佈局的播放進度相對應
    requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    //窗口無title,因爲該activity是以view的形式存在viewpager中,有title很突兀
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setVolumeControlStream(AudioManager.STREAM_MUSIC);
    //上次退出音樂播放器時,音樂列表的展開狀態
    if (icicle != null) {
        mCurrentAlbumId = icicle.getString("selectedalbum");
        mCurrentAlbumName = icicle.getString("selectedalbumname");
        mCurrentArtistId = icicle.getString("selectedartist");
        mCurrentArtistName = icicle.getString("selectedartistname");
    } else {
        Intent intent = getIntent();
        mCurrentAlbumId = intent.getStringExtra("selectedalbum");
        mCurrentArtistId = intent.getStringExtra("selectedartist");
    }

    //掃描媒體庫的廣播
    IntentFilter f = new IntentFilter();
    f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
    f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
    f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
    f.addAction(Intent.ACTION_MEDIA_MOUNTED);
    f.addDataScheme("file");
    registerReceiver(mScanListener, f);
    
    setContentView(R.layout.media_picker_activity_expanding);

    ExpandableListView lv = getExpandableListView();

    //長按一個view,會彈出View的上下文菜單
    lv.setOnCreateContextMenuListener(this);

    /*listview獲得焦點的時候,響應用戶在鍵盤輸入的匹配符,篩選出匹配的listview Items,這就跟搜索框的搜索功能相匹配了*/
    lv.setTextFilterEnabled(true);

    /*當設備的配置發生改變,比如橫豎屏的時候,它保存了當前activity的mAdapter的狀態,等到acitivty恢復的時候直接使用;
    * 相比於onSaveInstanceState和onRestoreInstanceState只能存取Bundle中對象,
    * onRetainNonConfigurationInstance與getLastNonConfigurationInstance的優點是可以存取任意對象,*/
    mAdapter = (ArtistAlbumListAdapter) getLastNonConfigurationInstance();
    if (mAdapter == null) {
        MusicLogUtils.d(TAG, "starting query");
        mAdapter = new ArtistAlbumListAdapter(
                getApplication(),
                this,
                null, // cursor
                R.layout.track_list_item_group,//group的佈局
                new String[] {},
                new int[] {},
                R.layout.track_list_item_child,//child的佈局
                new String[] {},
                new int[] {});
        //ExpandableList與數據進行綁定
        setListAdapter(mAdapter);
        setTitle(R.string.working_artists);
        PDebug.End("ArtistAlbumBrowserActivity.setListAdapter()");
        //異步查找數據,查找結束以後調用init(cursor)更新界面
        getArtistCursor(mAdapter.getQueryHandler(), null);
    } else {
        mAdapter.setActivity(this);
        mAdapter.reloadStringOnLocaleChanges();
        setListAdapter(mAdapter);
        mArtistCursor = mAdapter.getCursor();
        if (mArtistCursor != null) {
            /*前面已經調用了setListAdapter(mAdapter)進行了頁面的刷新,爲什麼還要init(mArtistCursor)-->mAdapter.changeCursor(c)再進行刷新一次呢?
*原因:分析過ListView源碼後知道,setListAdapter(mAdapter)的作用是給ListView的每個item設置view以及刷新ListView界面,
* 當數據源發生改變時調用該方法,並不會導致item界面的刷新,因爲item的數據是與cursor進行綁定的,要想item刷新,必須要通知mAdapter,
* 也就是調用mAdapter.notifyDataSetChanged(),這裏mAdapter.changeCursor(c)的本質也是mAdapter.notifyDataSetChanged()*/
            init(mArtistCursor);
        } else {
            getArtistCursor(mAdapter.getQueryHandler(), null);
        }
    }
}

點評:onCreate()方法主要是ExpandableList與數據進行綁定,綁定數據需要適配器,我們看下這個適配器ArtistAlbumListAdapter

ArtistAlbumListAdapter代碼比較多,我們只看下核心部分:

    static class ArtistAlbumListAdapter extends SimpleCursorTreeAdapter implements SectionIndexer {
        
        ..............
        ........
        class QueryHandler extends AsyncQueryHandler {
            QueryHandler(ContentResolver res) {
                super(res);
            }

            @Override
            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
                /*當數據查找結束以後,根據cursor更新item,更新item的核心方法是mAdapter.changeCursor(c)*/
                mActivity.init(cursor);
            }
        }

        ArtistAlbumListAdapter(Context context, ArtistAlbumBrowserActivity currentactivity,
                Cursor cursor, int glayout, String[] gfrom, int[] gto,
                int clayout, String[] cfrom, int[] cto) {
            super(context, cursor, glayout, gfrom, gto, clayout, cfrom, cto);
            mActivity = currentactivity;
            //用來異步查找數據庫
            mQueryHandler = new QueryHandler(context.getContentResolver());

            Resources r = context.getResources();

            mDefaultAlbumIcon = (BitmapDrawable) r.getDrawable(R.drawable.albumart_mp_unknown_list);

            mDefaultAlbumIcon.setFilterBitmap(false);
            mDefaultAlbumIcon.setDither(false);

            mContext = context;
            //設置字母索引的Cursor
            getColumnIndices(cursor);
            mResources = context.getResources();
            mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
            mUnknownAlbum = context.getString(R.string.unknown_album_name);
            mUnknownArtist = context.getString(R.string.unknown_artist_name);
        }

        private void getColumnIndices(Cursor cursor) {
            if (cursor != null) {
                mGroupArtistIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID);
                mGroupArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST);
                mGroupAlbumIdx =
cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS);
                mGroupSongIdx =
cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS);
                if (mIndexer != null) {
                    mIndexer.setCursor(cursor);
                } else {
                    /*MusicAlphabetIndexer繼承自AlphabetIndexer,AlphabetIndexer是一個字母索引輔助類,具體怎麼索引就不分析了,下面就看這幾個參數的意思:
                    * cursor包含了數據的對象,mGroupArtistIdx是進行索引排序的列號,
                    * mResources.getString(R.string.fast_scroll_alphabet)是一個字母表,根據這個字母表進行排序;
                    * 這裏的意思是:在搜索框中輸入文字時,會出現一個menu,menu中的歌手列表以歌手的名字在字母表中的順序進行索引*/
                    mIndexer = new MusicAlphabetIndexer(cursor, mGroupArtistIdx,
                            mResources.getString(R.string.fast_scroll_alphabet));
                }
            }
        }

        @Override
        public View newGroupView(Context context, Cursor cursor,
                boolean isExpanded, ViewGroup parent) {
            //創建一級ListView的佈局,也就是grouplist,佈局是track_list_item_group.xml;
            View v = super.newGroupView(context, cursor, isExpanded, parent);
            ImageView iv = (ImageView) v.findViewById(R.id.icon);
            ViewGroup.LayoutParams p = iv.getLayoutParams();
            p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
            p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
            ViewHolder vh = new ViewHolder();
            //歌手名
            vh.mLine1 = (TextView) v.findViewById(R.id.line1);
            //該歌手幾張專輯
            vh.mLine2 = (TextView) v.findViewById(R.id.line2);
            //最右邊的播放圖標
            vh.mPlayIndicator = (ImageView) v.findViewById(R.id.play_indicator);
            //最左邊的icon
            vh.mIcon = (ImageView) v.findViewById(R.id.icon);
            vh.mIcon.setPadding(0, 0, 1, 0);
            v.setTag(vh);
            return v;
        }

        @Override
        public View newChildView(Context context, Cursor cursor, boolean isLastChild,
                ViewGroup parent) {
            //創建二級ListView的佈局,也就是childlist,佈局是track_list_item_child.xml;
            View v = super.newChildView(context, cursor, isLastChild, parent);
            ViewHolder vh = new ViewHolder();
            //專輯名
            vh.mLine1 = (TextView) v.findViewById(R.id.line1);
            //該專輯下幾首歌曲
            vh.mLine2 = (TextView) v.findViewById(R.id.line2);
            /最右邊的播放圖標
            vh.mPlayIndicator = (ImageView) v.findViewById(R.id.play_indicator);
            //最左邊的icon被設置爲默認圖標mDefaultAlbumIcon
            vh.mIcon = (ImageView) v.findViewById(R.id.icon);
            vh.mIcon.setBackgroundDrawable(mDefaultAlbumIcon);
            vh.mIcon.setPadding(0, 0, 1, 0);
            v.setTag(vh);
            return v;
        }

        @Override
        public void bindGroupView(View view, Context context, Cursor cursor, boolean isexpanded) {
            //填充一級ListView的佈局,佈局是track_list_item_group.xml;
            ViewHolder vh = (ViewHolder) view.getTag();

            String artist = cursor.getString(mGroupArtistIdx);
            String displayartist = artist;
            boolean unknown = artist == null || artist.equals(MediaStore.UNKNOWN_STRING);
            if (unknown) {
                displayartist = mUnknownArtist;
            }

            //設置歌手名
            vh.mLine1.setText(displayartist);

            int numAlbums = cursor.getInt(mGroupAlbumIdx);
            int numSongs = cursor.getInt(mGroupSongIdx);

            String songsAlbums = MusicUtils.makeAlbumsLabel(context,
                    numAlbums, numSongs, unknown);

            //該歌手有幾張專輯
            vh.mLine2.setText(songsAlbums);

            long currentartistid = MusicUtils.getCurrentArtistId();
            long artistid = cursor.getLong(mGroupArtistIdIdx);

            //當前播放的音樂如果是此歌手的,那麼,group的最右邊需要個播放圖標
            if (currentartistid == artistid && !isexpanded) {
                vh.mPlayIndicator.setVisibility(View.VISIBLE);
            } else {
                vh.mPlayIndicator.setVisibility(View.GONE);
            }
        }

        @Override
        public void bindChildView(View view, Context context, Cursor cursor, boolean islast) {
            //綁定二級ListView的佈局,佈局是track_list_item_child.xml;
            //這裏面的cursor是從getChildrenCursor()方法中查找來的,我們需要重寫getChildrenCursor()方法
            ViewHolder vh = (ViewHolder) view.getTag();

            String name = cursor.getString(
                    cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
            String displayname = name;
            boolean unknown = name == null || name.equals(MediaStore.UNKNOWN_STRING);
            if (unknown) {
                displayname = mUnknownAlbum;
            }
            //專輯名
            vh.mLine1.setText(displayname);

            int numsongs = cursor.getInt(
            ............
            ............
            ............ 
            //專輯下幾首歌曲
            vh.mLine2.setText(builder.toString());

            ImageView iv = vh.mIcon;

            String art = cursor.getString(cursor.getColumnIndexOrThrow(
                    MediaStore.Audio.Albums.ALBUM_ART));
            //專輯圖標
            if (unknown || art == null || art.length() == 0) {
                iv.setBackgroundDrawable(mDefaultAlbumIcon);
                iv.setImageDrawable(null);
            } else {
                long artIndex = cursor.getLong(0);
                Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
                iv.setImageDrawable(d);
            }

            long currentalbumid = MusicUtils.getCurrentAlbumId();
            long aid = cursor.getLong(0);
            //設置最右邊的播放圖標的可見性
            iv = vh.mPlayIndicator;
            if (currentalbumid == aid) {
                iv.setVisibility(View.VISIBLE);
            } else {
                iv.setVisibility(View.GONE);
            }
        }
    }

 

3.除了數據加載,還有幾個重要的邏輯;

①當某個歌手有多張專輯的時候,點擊專輯item,會跳轉到系統選擇界面;

public boolean onChildClick(ExpandableListView parent, View v,int groupPosition, int childPosition, long id) {
    mCurrentAlbumId = Long.valueOf(id).toString();
    //系統選擇界面
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
    intent.putExtra("album", mCurrentAlbumId);
    Cursor c = (Cursor) getExpandableListAdapter().getChild(groupPosition, childPosition);
    String album = c.getString(c.getColumnIndex(MediaStore.Audio.Albums.ALBUM));
    if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
        mArtistCursor.moveToPosition(groupPosition);
        mCurrentArtistId = mArtistCursor.getString(
        mArtistCursor.getColumnIndex(MediaStore.Audio.Artists._ID));
        intent.putExtra("artist", mCurrentArtistId);
    }
    //跳轉到系統選擇界面
    startActivity(intent);
    return true;
}

 

②長按item,彈出上下文菜單

    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
        mSubMenu = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
        MusicUtils.makePlaylistMenu(this, mSubMenu);
        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);

        ExpandableListContextMenuInfo mi = (ExpandableListContextMenuInfo) menuInfoIn;

        int itemtype = ExpandableListView.getPackedPositionType(mi.packedPosition);
        int gpos = ExpandableListView.getPackedPositionGroup(mi.packedPosition);
        int cpos = ExpandableListView.getPackedPositionChild(mi.packedPosition);
        if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
            //當長按group的時候,創建的menu列表
            if (gpos == -1) {
                // this shouldn't happen
                MusicLogUtils.d(TAG, "no group");
                return;
            }
            gpos = gpos - getExpandableListView().getHeaderViewsCount();
            mArtistCursor.moveToPosition(gpos);
            mCurrentArtistId = mArtistCursor.getString(
                    mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
            mCurrentArtistName = mArtistCursor.getString(
                    mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
            mCurrentAlbumId = null;
            mIsUnknownArtist = mCurrentArtistName == null ||
                    mCurrentArtistName.equals(MediaStore.UNKNOWN_STRING);
            mIsUnknownAlbum = true;
            if (mIsUnknownArtist) {
                menu.setHeaderTitle(getString(R.string.unknown_artist_name));
            } else {
                //菜單的title
                menu.setHeaderTitle(mCurrentArtistName);
                menu.add(0, SEARCH, 0, R.string.search_title);
            }
            return;
        } else if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
            //當長按child的時候,創建的item
            if (cpos == -1) {
                // this shouldn't happen
                MusicLogUtils.d(TAG, "no child");
                return;
            }
            Cursor c = (Cursor) getExpandableListAdapter().getChild(gpos, cpos);
            c.moveToPosition(cpos);
            mCurrentArtistId = null;
            mCurrentAlbumId = Long.valueOf(mi.id).toString();
            mCurrentAlbumName = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
            gpos = gpos - getExpandableListView().getHeaderViewsCount();
            mArtistCursor.moveToPosition(gpos);
            mCurrentArtistNameForAlbum = mArtistCursor.getString(
                    mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
            mIsUnknownArtist = mCurrentArtistNameForAlbum == null ||
                    mCurrentArtistNameForAlbum.equals(MediaStore.UNKNOWN_STRING);
            mIsUnknownAlbum = mCurrentAlbumName == null ||
                    mCurrentAlbumName.equals(MediaStore.UNKNOWN_STRING);
            if (mIsUnknownAlbum) {
                menu.setHeaderTitle(getString(R.string.unknown_album_name));
            } else {
                menu.setHeaderTitle(mCurrentAlbumName);
            }
            //除去未知專輯和未知歌手,其他的情況下添加搜索菜單
            if (!mIsUnknownAlbum || !mIsUnknownArtist) {
                menu.add(0, SEARCH, 0, R.string.search_title);
            }
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            //"播放"item
            case PLAY_SELECTION: {
                long [] list =
                    mCurrentArtistId != null ?
                    MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
                    : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));

                MusicUtils.playAll(this, list, 0);
                return true;
            }

            case QUEUE: {
                long [] list =
                    mCurrentArtistId != null ?
                    MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
                    : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
                MusicUtils.addToCurrentPlaylist(this, list);
                return true;
            }

            case NEW_PLAYLIST: {
               
                if (mCurrentArtistId != null) {
                    showCreateDialog("selectedartist_" + mCurrentArtistId,
                                     MusicBrowserActivity.ARTIST_INDEX,
                                     MusicUtils.NEW_PLAYLIST);
                } else if (mCurrentAlbumId != null) {
                      showCreateDialog("selectedalbum_" + mCurrentAlbumId,
                                     MusicBrowserActivity.ARTIST_INDEX,
                                     MusicUtils.NEW_PLAYLIST);
                }
                return true;
            }

            //"添加到播放列表"item
            case PLAYLIST_SELECTED: {
                long [] list =
                    mCurrentArtistId != null ?
                    MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
                    : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
                long playlist = item.getIntent().getLongExtra("playlist", 0);
                MusicUtils.addToPlaylist(this, list, playlist);
                return true;
            }

            //"刪除"item
            case DELETE_ITEM: {
                long [] list;
                Bundle b = new Bundle();
                if (mCurrentArtistId != null) {
                    list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId));
                    b.putInt(MusicUtils.DELETE_DESC_STRING_ID, R.string.delete_artist_desc);
                    b.putString(MusicUtils.DELETE_DESC_TRACK_INFO, mCurrentArtistName);
                   
                } else {
                    list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
                    b.putInt(MusicUtils.DELETE_DESC_STRING_ID, R.string.delete_album_desc);
                    b.putString(MusicUtils.DELETE_DESC_TRACK_INFO, mCurrentAlbumName);
                }
                b.putLongArray("items", list);
                Intent intent = new Intent();
                intent.setClass(this, DeleteItems.class);
                intent.putExtras(b);
                startActivityForResult(intent, -1);
                return true;
            }

            //"搜索"item
            case SEARCH:
                doSearch();
                return true;
        }
        return super.onContextItemSelected(item);
    }

 

總結:ArtistAlbumBrowserActivity沒什麼好說的,重點就是這些了

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章