Android用(ちょっと)本格的なミュージックプレーヤの開発 part5

ミュージックプレーヤの開発 part5です。

前回、フラグメントの積み上げの管理をできるようにして、
アルバムメニューを表示したので、今回はアーティストメニューを作ります。

補足あります

アーティストメニューの呼び出し

アーティスト用のメニューですが、基本的には前回の
アルバムメニューを改造すれば表示までは簡単にできます。

まず、
enum FrgmType { fRoot, fAlbum, fArtist }
として、アーティスト用画面を登録します。

続いて、

 private Artist focusedArtist;
 public void focusArtist(Artist item) {if(item != null) focusedArtist = item;}
 public Album getFocusedArtist() {return focusedArtist ;}

以上3行の追加でアーティストの情報も簡易的に取り出せるようにします。

最期に

public AdapterView.OnItemClickListener ArtistClickListener
public AdapterView.OnItemLongClickListener ArtistLongClickListener

を作成します。
これも Album用に作ったものをコピーして Album→Artist に書き換えればOKです。
そして
RootMenuのArtistSectionFragmentにて

artistList.setOnItemClickListener(activity.ArtistClickListener);
artistList.setOnItemLongClickListener(activity.ArtistLongClickListener);

登録すれば完了です。
あとはMainアクティビティの setNewFragment にて

        switch(CallFragment)
        {
         case fRoot   : ft.replace(R.id.root, new RootMenu(),     "Root"); break;
         case fAlbum  : ft.replace(R.id.root, new AlbumMenu(),   "album"); break;
         case fArtist : ft.replace(R.id.root, new ArtistMenu(), "artist"); break;
        }

このようにすればいいのですが、まだ ArtistMenu はできていないのでこの時点で追加すると
赤波線がついてしまいます。 前回と同じように /ダミー/ を使ってもいいとおもいます。

以上でアーティストメニューが呼び出せるようになりました。

アーティストメニューの設計

呼び出し部分ができたので早速アーティストメニューを作っていきます。
基本的にはアルバムメニューと同じく、 getFocusedArtist() で指定された
アーティスト情報を読みだして、その情報をもとに画面を生成すればいいわけです。

ただ、
アルバムメニューでは、アルバムに登録されていたトラックをそのまま表示すれば
良かったのですが、アーティストの場合

 アーティスト → 複数のアルバム → 複数のトラック

と、2種類の表示単位が考えられるので、1画面で効率よく表示する工夫が
必要になってきます。

アプリによって様々ですが、現状で簡単な方法としては、
  アーティストメニューには、アーティストが参加するアルバムの一覧を表示しておいて
  そのアルバムを選択すると、前回作成したアルバムメニューが更に開く。
という方法などもあります。

今回はそれでは面白く無いので以下の様な仕様にしました。


アーティストメニューをひらくと
指定されたアーティストの全てのトラックのリストを表示


"全てのトラック" の部分は開閉可能となっており
開くと、アーティストが参加しているアルバムのリストが表示


アルバムを選択すると、そのアルバム内のトラックが
一覧に更新される

最期の画像をみれば分かる通り、アルバムを選択した場合、
指定されたアーティスト以外のアーティストの曲もすべて表示するように
してあります。
このようにしたのは、


トラック名 - 登録アーティスト
トラック1 - アーティストA
トラック2 - アーティストB
トラック3 - アーティストA / アーティストB
トラック4 - アーティストA with ゲストC

などと登録されていることがよくあり、 厳格にアーティスト名を一致させると
トラック3,4が表示されなかったりするので、このようにしました。

アーティストの実装

では、
仕様が決まったので実際にコードで書いていきます。

まずは、上の画像で見えているようにアルバム用のサブメニューを
作るために spinner のUIをカスタマイズします。

まずは
drawable のフォルダ内に boder.xml というデータを作り

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ffffff">
      <stroke android:width="1px" android:color="#111111">
    </stroke></solid>
</shape>

このように記述します。
続いて layoutに以下2つのデータを追加します。

●spinner_dropdown_item.xml

<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="?android:attr/spinnerDropDownItemStyle"
    android:layout_width="match_parent"
    android:layout_height="35dp"
    android:background="@drawable/boder"
    android:ellipsize="marquee"
    android:singleLine="true"
    android:text="spinner"
    android:textColor="#111111" />

●spinner.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="?android:attr/spinnerItemStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="marquee"
    android:gravity="center"
    android:singleLine="true"
    android:text="spinner"
    android:textColor="#111111"
    android:textSize="12sp" />

背景(background)に@drawable/boderを指定することで、簡単に縁付き領域が
できます。

準備ができたら例によって

レイアウトを作成します。

必要なデータがそろったので処理を書いていきます
ArtistMenuというクラスを新しく作って処理を書いていきます。

これまで作った中では比較的複雑です。
長いので必要な部分ごとに掲載します。

        private static Artist artist_item;
        private final static String default_msg = "全てのトラック";
        private static ListTrackAdapter track_adapter;
        private ImageView album_art;
        private View partView;
        private static HashMap album_hash = new HashMap(); 

宣言はこんなかんじです。
artist_item に activity.getFocusedArtist() で指定されたアーティスト名を格納します。
デフォルトメッセージは簡易的にソースコードに直接 "全てのトラック" と書いて
わかりやすくしてありますが、res/value/strings.xml に記述したほうがベターです。

HashMap album_hash で、アーティストが参加しているアルバムの一覧を
管理します。

処理手順は以下の通りです。

●アルバムメニューと同じようにタイトルや アルバム数トラック数の情報を表示する

●spinnerの準備
先ほど準備したレイアウトを使ってスピナを生成します。

        ArrayAdapter adapter = new ArrayAdapter(activity,R.layout.spinner_item);
        adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
        adapter.add(default_msg);

最初の候補として add(default_msg) しておきます。

●一覧用トラックリストの生成と初期化

●指定アーティスト名でトラックを検索
以前アルバムIDからトラックを検索する機能を書きましたが
今回はアーティスト名で検索します。
この時、album_hashにそれぞれのトラックが所属するアルバムを記録しておきます。

                String[] SELECTION_ARG = {""};
                SELECTION_ARG[0] = artist_item.artist;

                ContentResolver resolver = activity.getContentResolver();
                Cursor cursor = resolver.query(
                                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
                                Track.COLUMNS, 
                                MediaStore.Audio.Media.ARTIST + "= ?",
                                SELECTION_ARG,
                                "TRACK  ASC"
                                );
                album_hash.clear();
                while( cursor.moveToNext() ){
                        if( cursor.getLong(cursor.getColumnIndex( MediaStore.Audio.Media.DURATION)) < 3000 ){continue;}
                        tracks.add(new Track(cursor));
                        album_hash.put(
                                        cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.ALBUM )), 
                                        String.valueOf(cursor.getLong( cursor.getColumnIndex( MediaStore.Audio.Media.ALBUM_ID )))
                                        ); 
        }

基本的な方法は Track. getItemsByAlbum() と変わりませんが
 tracks.add(new Track(cursor)) するついでに
 album_hash.put( アルバム名,アルバムID )
も実行しておきます。 ハッシュマップをもちいることで重なりなく アルバムのリストも
同時に生成できるわけです。

●spinnerに登録
出来上がったアルバムの一覧をスピナーに登録します。
また、スピナーから特定のアルバムが選択された場合、
指定されたアルバム情報を取得するようにします。

          Set<Entry<String, String>> s = album_hash.entrySet();
          for (Iterator<Entry<String, String>> i = s.iterator(); i.hasNext();) {
              adapter.add( objectStrip( (String)i.next().toString() ) );

            }
         Spinner spinner = (Spinner) partView.findViewById(R.id.album_spinner);
         spinner.setAdapter(adapter);
         spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view,
                    int position, long id) {
                Spinner spinner = (Spinner) parent;
                String item = (String) spinner.getSelectedItem();               
                if(item.equals(default_msg)) setList(null);
                else setList(item);
                String path = ImageGetTask.searchArtPath(getActivity(),item);

                album_art = (ImageView)partView.findViewById(R.id.albumart);
                album_art.setImageResource(R.drawable.dummy_album_art_slim);
                        if(path!=null){
                                album_art.setTag(path);
                                ImageGetTask task = new ImageGetTask(album_art);
                                task.execute(path);
                        }
            }
            @Override
            public void onNothingSelected(AdapterView<?> arg0) {
            }
       });

最初の for でアルバムタイトルをスピナーに登録して、
それ以降でアルバム選択時の処理が書かれています。
objectStripという関数は ハッシュの内容をイテレータを用いて順次取得したものを
アルバム名部分だけに切り取ります。
実装は以下の通りシンプルです。

        private String objectStrip(String base){
            if (base == null)
                return null;
            int point = base.lastIndexOf("=");
            if (point != -1) {
                return base.substring(0, point);
            } 
            return base;
        }

続いて、メニューが選択されたときの処理です。

デフォルトメッセージが選択されている場合は setList(null) だけ実行
それ以外(=アルバム名が指定されている場合) は、setList(指定アルバム名)に加えて
アルバムアートを表示します。 
アルバムアート表示部分は part2記事 で扱った方法と同じです。

この部分のために ImageGetTask に小さな機能、
アルバムタイトルからコンテントプロバイダ経由でアルバムアートを取得する

ImageGetTask.searchArtPath();

を追加しておきました。

setList() は、
 -アルバム名を与えるとそのアルバムに登録されているトラックの、--
 -アルバム名がNULLの場合は指定されているアーティストの曲全ての、--
リストを生成して track_adapter に更新します。

private void setList(String item){
                Main activity = (Main)getActivity();
                ContentResolver resolver = activity.getContentResolver();
                track_adapter.clear();
                List tracks = new ArrayList();
                String[] SELECTION_ARG = {""};

                if(item == null)
                {
                        SELECTION_ARG[0] = artist_item.artist;

                        Cursor cursor = resolver.query(
                                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
                                        Track.COLUMNS, 
                                        MediaStore.Audio.Media.ARTIST + "= ?",
                                        SELECTION_ARG,
                                        "TRACK ASC"
                                        );
                        while( cursor.moveToNext() ){
                                if( cursor.getLong(cursor.getColumnIndex( MediaStore.Audio.Media.DURATION)) < 3000 ){continue;}
                                        tracks.add(new Track(cursor));          
                        }
                }else{
                        SELECTION_ARG[0] = album_hash.get(item).toString();
                        Cursor cursor = resolver.query(
                                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
                                        Track.COLUMNS, 
                                        MediaStore.Audio.Media.ALBUM_ID + "= ?",
                                        SELECTION_ARG,
                                        null
                                        );
                        while( cursor.moveToNext() ){
                                if( cursor.getLong(cursor.getColumnIndex( MediaStore.Audio.Media.DURATION)) < 3000 ){continue;}
                                tracks.add(new Track(cursor));
                        }
                }

                track_adapter.addAll(tracks);
                track_adapter.notifyDataSetChanged();
        }

こんなかんじです。さんざん似たコードを書いてきたので難しくはないと思います。
最後の tarack_adapter.addAll() は API Level 11以降(たしか…)対応なので注意です。
(今さら そんなに古いものに対応することもないと思いますが…)

以上で処理部分も完成です。MainアクティビティのsetNewFragmentを
更新すれば呼び出せるようになります。

エミュレータで起動すると

狙い通り動作しているのが確認できました。

お疲れ様です。
これでライブラリ部分は検索機能をのこして主な機能は網羅されたと思います。
次回は 検索機能を作りたいと思います。

おまけ:ここまでのアプリ本体(apk)

コメントを追加する