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

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

ライブラリパートはこれでとりあえず完了です。 実はプレイリスト管理という大仕事が残っていますが

プレイリストについては、音楽再生サービスが出来たあとのほうが 整理しやすいのでひとまずおいておきます。

検索機能

検索機能の実装はライブラリ関連機能のなかでは一番大規模です。 ただし実装の難易度はそんなに高くありません。 順に作っていきます。

呼び出し

呼び出し部分はいつもの通りです。 Mainアクティビティにて

enum     FrgmType { fRoot, fAlbum, fArtist, fSearch }

まずはフラグメントタイプに登録して、 検索ワード保持用に

public   static String     searchWord;
public String getSearchWord()          {return searchWord;}

最後に 任意の場所にテキストフィールドと検索ボタンを設置、 ボタンが押されると以下の関数が実行されるようにします。

onClick(View v)で、 vが検索ボタンならばソフトウェアキーボードを終了して setSearch() を呼び出します。

        InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
        setSearch();

setSearch()は以下の通りです。 searchTextは設置したEditTextウィジェットです。

        public void setSearch(){
                searchWord = searchText.getText().toString();
                if(searchWord.isEmpty()) return;
                setNewFragment(FrgmType.fSearch);
        }

空入力でないことを確認してから 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;
         case fSearch : ft.replace(R.id.root, new SearchMenu(), "search"); break;
        }

setNewFragmentに検索用画面呼び出し用の処理を追記します。 当然 SearchMenuはまだできていないので赤波線になるので必要ならダミーを使って ください。

検索アイテム

検索画面では、 入力された文字列を含む  トラック、アルバム、アーティスト を検索して表示します。 ことなる3パターンのデータをうまい具合に1つのリスト内に表示したいので、 従来の Track Album Artist のアイテムリストでは不都合です。 新たに Search アイテムを追加します。中身はシンプルです。

public class Search {
        enum ItemType {
                TRACK,
                ALBUM,
                ARTIST
        }
        public ItemType itemType;
        public Track    trackItem;
        public Album    albumItem;
        public Artist   artistItem;
        public String   title;
        public String   sub;

        public Search(ItemType type, Cursor cursor)
        {
                itemType = type;
                switch(type)
                {
                        case TRACK:
                                trackItem = new Track(cursor);
                                title = trackItem.title;
                                sub   = trackItem.artist;
                                break;
                        case ALBUM:
                                albumItem = new Album(cursor);
                                title = albumItem.album;
                                sub   = albumItem.artist;
                                break;
                        case ARTIST:
                                artistItem = new Artist(cursor);
                                title = artistItem.artist;
                                sub   = "Artist" ;
                                break;

                }

        }

}

保持しているデータのタイプとそのデータを保持します。

Searchを扱うリストのアダプタも追加します。 これもシンプルです。

まずはxmlでリストアイテムのデザインを組みます。

このレイアウトに合わせてListSearchAdapterを実装します。

public class ListSearchAdapter extends ArrayAdapter{
LayoutInflater mInflater;

        public ListSearchAdapter(Context context, List item){
                super(context, 0, item);
                mInflater =  (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
        }

        @Override
        public View getView(int position, View convertView,ViewGroup parent){

                Search item = getItem(position);        
                ViewHolder holder;

                if(convertView==null){
                        convertView = mInflater.inflate(R.layout.item_search, null);
                        holder = new ViewHolder();
                        holder.titleTextView  = (TextView)convertView.findViewById(R.id.title);
                        holder.subTextView    = (TextView)convertView.findViewById(R.id.sub_title);
                        holder.typeImageView  = (ImageView)convertView.findViewById(R.id.typeart);
                        convertView.setTag(holder);
                }else{
                        holder = (ViewHolder) convertView.getTag();
                }

                holder.titleTextView.setText(item.title);
                holder.subTextView.setText(item.sub);

                switch(item.itemType){
                        case  TRACK: holder.typeImageView.setImageResource(R.drawable.search_track);  break;
                        case  ALBUM: holder.typeImageView.setImageResource(R.drawable.search_album);  break;
                        case ARTIST: holder.typeImageView.setImageResource(R.drawable.search_artist); break;
                }

                return convertView;     
        }

        static class ViewHolder{
                TextView   titleTextView;
                TextView   subTextView;
                ImageView  typeImageView;
        }
}

基本的にトラックやアルバム、アーティストのアダプタと同じです。

        switch(item.itemType){
                case  TRACK: holder.typeImageView.setImageResource(R.drawable.search_track);  break;
                case  ALBUM: holder.typeImageView.setImageResource(R.drawable.search_album);  break;
                case ARTIST: holder.typeImageView.setImageResource(R.drawable.search_artist); break;
        }

この部分は検索アイテムのデータタイプ識別アイコンの切り替えです。

こんなかんじのアイコンをGIMPで作成しました。 アイテムのタイプで表示するアイコンを変更します。

以上で検索機能に必要なパーツが揃いました。

検索

では早速検索画面を作ります。 例によってレイアウトを組みます。

前回作成した spinnerをここでも使用します。 spinnerには 全て、トラック、アルバム、アーティスト の4つの抽出モードを 登録しておいて切り替えられるようにします。

レイアウトに合わせて SearchMenu を作成します。 長いので部分ごとに掲載します。

まずは宣言

                private static Main activity;
                private String searchWord;
                private String[] SearchSelecter = 
                                {"全て" ,"トラック","アルバム" , "アーティスト"};

                private static List<Search>  results = null;
                private static ListSearchAdapter result_adapter = null;

前回同様ソースに直接 全て〜 を書き込んでいますが res/value/strings.xml を使ったほうがベターです。 onCreateViewでは、いつものように

 activity = (Main)getActivity();
 searchWord = activity.getSearchWord();

次は検索結果表示用に ListViewを初期化します。

ListView resultList = (ListView) searchView.findViewById(R.id.list);
                 result_adapter = new ListSearchAdapter(activity,results);
                 resultList.setAdapter(result_adapter);
                 resultList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                        @Override
                        public void onItemClick(AdapterView parent, View view,
                                        int position, long id) {
                                                 ListView lv = (ListView)parent;
                                                 Search item = (Search)lv.getItemAtPosition(position);
                                                 switch(item.itemType)
                                                 {
                                                 case TRACK:
                                                         activity.focusTrack(item.trackItem);
                                                         break;
                                                 case ALBUM:
                                                         activity.focusAlbum(item.albumItem);
                                                         activity.setNewFragment(FrgmType.fAlbum);
                                                         break;
                                                 case ARTIST:
                                                         activity.focusArtist(item.artistItem);
                                                         activity.setNewFragment(FrgmType.fArtist);
                                                 }
                                        } 
                                        });
                 resultList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
                        @Override
                        public boolean onItemLongClick(AdapterView parent, View view,
                            int position, long id){
                                                ListView lv = (ListView)parent;
                                                Track item = (Track)lv.getItemAtPosition(position);
                                                Toast.makeText((Main)getActivity(), "LongClick:"+item.title, Toast.LENGTH_LONG).show();
                                        return true;    
                        }
                        });

アイテムが選択されると、アイテムタイプを識別して 選択されたアイテムをフォーカスして setNewFragment を呼び出します。

最後にsipnnerを設定します。

                ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity,R.layout.spinner_item);
                adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
                adapter.addAll(SearchSelecter);
                Spinner spinner = (Spinner) searchView.findViewById(R.id.option_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();
                        result_adapter.clear();
                        setList(item);
                                result_adapter.notifyDataSetChanged();
                    }
                    @Override
                    public void onNothingSelected(AdapterView<?> arg0) {
                    }
                });

前回用意したデザイン(xml)を使ってスピナを登録します。 スピナでモードが選択されると、 setList が呼ばれます。

setListはシンプルです。

        private void setList(String item){
                results.clear();
                if(item.equals(SearchSelecter[0])) //全て
                {
                        searchArtist();
                        searchAlbum();
                        searchTrack();

                }else if(item.equals(SearchSelecter[1])) //トラック
                {
                        searchTrack();
                }else if(item.equals(SearchSelecter[2])) //アルバム
                {
                        searchAlbum();
                }else if(item.equals(SearchSelecter[3])) //アーティスト
                {
                        searchArtist();
                }

        }

選択された内容ごとに検索機能を呼び出します。 searchArtist(), searchAlbum(), searchTrack() は名前の通り、 searchWord に基づいてアーティスト、アルバム、トラックを検索します。

それぞれの実装はほぼ同じですが、 検索の範囲だけが少し違います。

アーティストの検索では  指定された文字列を含むアーティスト だけを探しますが、

アルバムの検索では  指定された文字列を含むアルバム  指定された文字列を含むアーティストが登録されたアルバム も探します。

また トラックの検索では  指定された文字列を含むトラック  指定された文字列を含むアルバムに登録されたトラック  指定された文字列を含むアーティストに登録されたトラック

を検索するようにします。

        private void searchTrack(){

                 HashSet set = new HashSet();
                ContentResolver resolver = activity.getContentResolver();       
                String[] SELECTION_ARG = {"%"+searchWord+"%", 
                                          "%"+searchWord+"%",
                                          "%"+searchWord+"%" };
                Cursor cursor = resolver.query(
                                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
                                Track.COLUMNS, 
                                MediaStore.Audio.Media.TRACK + " like ? OR "
                             +MediaStore.Audio.Media.ALBUM + " like ? OR "
                             +MediaStore.Audio.Media.ARTIST+ " like ?",
                                SELECTION_ARG,
                                null
                                );
        while( cursor.moveToNext() ){
                if( cursor.getLong(cursor.getColumnIndex( MediaStore.Audio.Media.DURATION)) < 3000 ){continue;}
                if( set.contains(cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.TITLE ))))     {continue;}
                        results.add(new Search(Search.ItemType.TRACK,cursor));
                        set.add(cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.TITLE )));
        }
        cursor.close();
        }

ということでトラックの検索はこんなかんじです。 通常の検索では 完全一致 のデータしか持ってこないので 一部一致でヒットするように  like そして OR でつないで 上記の3つの条件で検索しています。

album artist については検索オプションの OR の数が違う以外はほぼ同じです。

以上で全実装完了です。 エミュレータで起動してみます。

検索できました!

"a" で検索すると、条件にマッチする トラック、アルバム、アーティストが表示されており spinnerから抽出モードを切り替えることで アーティストのみの検索になったのが確認できます。

以上で基本的な音楽ライブラリ機能の実装が完了しました。

次回からいよいよ音を出すための機能を作っていきます。