ミュージックプレーヤの開発 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から抽出モードを切り替えることで アーティストのみの検索になったのが確認できます。
以上で基本的な音楽ライブラリ機能の実装が完了しました。
次回からいよいよ音を出すための機能を作っていきます。