以下の質問に対する補足です。
part5での処理の流れあたりでつまづいています。 (合っているかわからない)タイトルや アルバム数トラック数の情報を表示プログラミング
(合っているかわからない)一覧用トラックリストの生成と初期化
上2つは一応書けていてエラーもないので、あっているていで進めても
spinnerの登録はどこに書けばいいのかわからない始末です。。。
part5の手順を確認し、基礎事項の説明を交えてた補足を行います。
part5の内容
Part5でやっていることは アーティスト用のメニューの作成です。 大まかな手順は以下の通り
- Mainアクティビティに新しいタイプのフラグメントの受け入れ準備
- UIパーツ(spinner関連)の準備
- レイアウトの構成
- フラグメントを供給するArtistMenuクラスの作成
これだけです。
Mainアクティビティの処理
作成中のアプリは1つのアクティビティの持つViewの大部分をフラグメント用の領域に割り当てて フラグメント領域に色々な画面を表示することで画面遷移を作っています。
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;
}
ここがまさにそれで 例えば fRootならば メインアクティビティが持っているメイン画面に割り当てた R.id.root という領域に RootMenu() が用意する フラグメントの画面(View)を 差し込んで下さい という命令です。
このような作りなので 新しい画面が作りたい場合は
Fragment画面を用意するクラスを用意して Mainアクティビティに登録する
という手順を踏むように統一します。 また、各フラグメントで発生するタッチイベントをどうするか、 ですが、どのフラグメントにタッチされても メインアクティビティで集中管理するようにします。
というのは、例えば トラックが選択される という処理は アルバムメニューやアーティストメニュー など違うフラグメント 上で発生しますが、処理は統一できるからです。
よって
Mainアクティビティに
- enum { ... , fArtist} を追加
- focusedArtist focusArtist(Artist item) getFocusedArtist() を追加 …これはフラグメントから上位のアクティビティにデータを要求するために用意します
- アーティスト用ClickListener を追加
という作業を行えばいいわけです。
メニューの設計
これは本記事の通りです。 いい感じにしてください。
アーティストメニューに必要な情報を考えると
- アーティストの情報
- アルバムの選択
- トラックの一覧 これくらいだと思います。 これを実現できればなんでも良いわけです。
アイデアがない場合は アルバムやトラックリストを別画面にしたっていいわけです。 例えば
- アーティストIDを指定すると、そのアーティストのアルバムリストを表示するフラグメント
- アルバムIDを指定すると、そのアーティストのトラックリストを表示するフラグメント
なんてものを作ってMainアクティビティに登録すればいいだけですので part1 〜 4 で作った トラックメニューやアルバムメニューにちょっと手を加えるだけで コピペ作業に落とし込めることも分かると思います。
フラグメント間でアルバムIDなどをやり取りする必要がありますが そのために focusedAlbum などの仕組みがMainアクティビティに用意してあります。
アーティストメニューはアルバムのリストだけを表示 タッチされたアルバムのIDを focusedAlbum に記録するように頼んで ↓ 親アクティビティのfocusedAlbumを取得して そのアルバムのトラック一覧を表示するフラグメント を呼び出し
こうすれば spinner は使わなくてすみます。 ただリストを表示するだけなので part1~3 のレイアウトを そのまま流用できます。
アルバムIDで限定されたList
ArtistMenuの作成
ここが本丸ですね。
まず最小限必要なものを用意します。 ArtistMenu() の仕事は フラグメント用の画面を1枚仕上げること です。
アクティビティから呼んでもらえるように Fragmentクラスを継承した(= exxtend Fragment) クラスを用意します
Viewの話
ここで基礎事項の確認です。 Androidの画面は基本的に View で構成されます。 Viewの中にView が入れ子したり重なったりしているだけで すべてView です。 FragmentやActivityも特殊なViewとみなしてしまいます。 ListViewなども当然Viewです。
Viewを継承し、それに機能追加したものが ListView や他のカスタムView ですから全部同じように考えてOKです。
Viewを継承したクラスは、 誕生した瞬間にViewを用意することが宿命付けられています ので Viewが生成された時に呼び出される onCreateView() を必ず持っています。 継承元の View の onCreateView() は空View しか返さないので
この onCreateView() を書き換えるのが基本 です。
つまり
public class なんとか extend (View系)
{
@Override
public onCreateView()
{
どうにかしてViewを用意
return (用意したView)
}
}
これが基本型です。 RootMenuや RootMenuの中の TrackSectionFragment Mainアクティビティ ですらこの形が保たれていると思います。 これらはすべて Viewを提供する という機能を継承しているからです。
オリジナルのViewが作りたい時も これを守れば適当に作れます。
Inflator の話
inflate という名前から分かる通り(?) つまり in + flu/fla : なかに + 流し込んで -> 膨らませる という意味ですが、
Inflator は 外部のデータなどからViewをしたてて 流し込んでくれる 働きをしています。
基本的には xmlで用意したレイアウトファイルから Viewの雛形を 生成してくれるわけでかなり重宝します。
以上吹き出し2つから
一番基本のViewの作り方は
public class なんとか extend (View系)
{
View myView;
@Override
public onCreateView()
{
myView = inflator.inflate(レイアウトのxmlや必要なデータ);
ここから雛形状態の myView を完成させる
例えば
レイアウトXMLにIDを指定したViewが配置してある場合
myView.findViewByID(R.id.なんちゃら)
でテンプレート内の特定のViewが捕まえられます。
通常は Viewを拡張した TextView や ImageView だと
思うので キャストを行って使います
つまり
TextView msg = (TextView)myView.findViewByID(R.id.なんちゃら);
msg.setText("サンプルだよ!");
これでいいわけです。
インフレータで作った雛形状態の myView の内、IDで指定した部分の
Viewを捕まえて、部分的に書き換えることで必要な画面を作ったわけです。
msg は Viewを拡張した TextView なので テキストを設定すると
Viewの中に文字が反映される特殊機能付きのViewです。(あくまでView!)
なので通常Viewを拡張した msg.setText("メッセージ") が使えます。
return myView;
}
}
となるわけです。 とすれば
今回作る AltistMenu も
public class ArtistMenu extends Fragment{
private View partView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
partView =inflater.inflate(R.layout.part_artist, container, false);
ここで現在雛形が生成された partView に実際のデータを
流し込んで partView を完成させる
return partView; ←完成したView を返して完了!
}
}
まずこれは何も考えずにかけるわけです。
では、次に 雛形を完成させる部分を作りこめばいいだけです。
まずはアクティビティ先輩から現在選択中のアーティストをもらい、 雛形に反映させてみましょう。
public class ArtistMenu extends Fragment{
private View partView;
private static Artist artist_item;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
partView =inflater.inflate(R.layout.part_artist, container, false);
ここから雛形を完成
アクティビティを捕まえて、アーティストをもらう
Main activity = (Main)getActivity();
artist_item = activity.getFocusedArtist();
もらったアーティストから雛形を埋める
TextView artist_title = (TextView) partView.findViewById(R.id.title); -|
TextView artist_albums = (TextView) partView.findViewById(R.id.albums); |…各部分のViewを捕まえて
TextView artist_tracks = (TextView) partView.findViewById(R.id.tracks);-|
artist_title.setText(artist_item.artist); -|
artist_albums.setText(String.valueOf(artist_item.albums)+"Albums"); |…それぞれテキストを設定するだけ!
artist_tracks.setText(String.valueOf(artist_item.tracks)+"Tracks"); -|
return partView; ←完成したView を返して完了!
}
}
これでいいでしょう。 Viewを捕まえて書き換えるイメージを掴んで下さい。
テンプレートから アルバムタイトル アルバム数 トラック数 だけ書き換わっていると思います。
基本はこれがすべてです。 spinner も同じようでにできます。
…が、 spinner や ListView はちょっとだけ面倒です。というのは spinnerやListViewは、これら自体が 孫View群(ArrayList)を抱えています。
よって ListViewやSpinnerに孫View群を接続する必要があり、 これを提供するのが ArrayAdapter というわけです。 TrackやAlbumリストで作っていたのがこれです。
ArrayAdaptorは getView というメソッドを必ずもっており .getView(ほしい子要素の番号,書き込んで欲しいView)
と指定すると その子要素 View (またView!) を作成して 返すようになっています。 通常は TextView 1つを返してくるのですが この getView を Override して Text 以外の View を返すように すれば、カスタムリストが作れるわけです。 Track や Album のリストはそうやって作られています。
さて、今回の spinner に関して言えばカスタムリストでなくて通常の TextView
を管理してくれる ArrayAdapter
まず
private View partView;
private static Artist artist_item;
↓
private View partView;
private static Artist artist_item;
private final static String default_msg = "全てのトラック";
private static ListTrackAdapter track_adapter;
private ImageView album_art;
private static HashMap<String,String> album_hash = new HashMap<String,String>();
必要な宣言を↑この通り追加します。
そうしたら onCreateView にどんどん追記していきます。
ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity,R.layout.spinner_item);
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
adapter.add(default_msg);
こうします String型の配列から 孫View群の作成管理をしてくれる ArrayAdapterを用意して ドロップダウン時の見た目を指定するレイアウトをわたし 早速 デフォルト文字列を追加 しているだけです。
次に
List<Track> tracks = new ArrayList<Track>();
tracks.clear();
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 )))
);
}
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() ) );
}
この部分は検索パートです。 基本的には TrackリストやAlbumリストでおこなったコンテントプロバイダへの 要求とそっくりです。Trackなどでは Track.add() するだけでしたが 今回は album_hash にアルバム名を記録しています。 ハッシュマップを使うことで被りなくアルバム名のリストができるわけです。 ここで登場している objectStrip() は本記事に記載してあります。 検索結果からアルバム名だけを切り取るメソッドです。
最後に
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) {
}
});
このパートです 最初の2行はいいでしょう。 基本の基本 SpinnerのViewを捕まえて、そのViewに孫View群を管理するAdapterを接続 次に Spinnerでアイテムが選択された時に発生するイベントを キャッチするためのリスナーを作成しています。
アイテムが選択されたら、 Spinnerから選択されたアイテムを取得して デフォルトメッセージ以外なら album名のはずとして setList()で下部メイン領域にトラック一覧を更新 アルバム画像を持ってきて 基本形つまり アルバム表示用のViewを捕まえて画像登録
としているだけです。 setListの中身は本記事に掲載してあります。
最後の最後、 アルバム選択が発生する前の アーティストのすべてのTrackを表示するために
ListView trackList = (ListView) partView.findViewById(R.id.list);
track_adapter = new ListTrackAdapter(activity, tracks);
trackList.setAdapter(track_adapter);
trackList.setOnItemClickListener(activity.TrackClickListener);
trackList.setOnItemLongClickListener(activity.TrackLongClickListener);
記事part1 と同じ処理を追加しました。 ↑3行はもはや当然 雛形からリスト領域のViewを捕まえて Trackアイテムを表示するように拡張したAdaptorを作成 それを登録
下2行が 各Trackが選択された時に呼び出すリスナの設定で Mainアクティビティに作ってある Track選択リスナ を指定しています。
これが冒頭に書いたTrack選択の一本化 の効果で こうすることでこの5行をコピペするだけで AlbumメニューでもこのArtistメニューでも他の再生リストでも タップやロングタップの処理が Mainアクティビティを経由して 一本化されます。
以上で ArtistMenu のクラスは完全に完成です。
public class ArtistMenu extends Fragment{
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<String,String> album_hash = new HashMap<String,String>();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
partView =inflater.inflate(R.layout.part_artist, container, false);
Main activity = (Main)getActivity();
artist_item = activity.getFocusedArtist();
TextView artist_title = (TextView) partView.findViewById(R.id.title);
TextView artist_albums = (TextView) partView.findViewById(R.id.albums);
TextView artist_tracks = (TextView) partView.findViewById(R.id.tracks);
artist_title.setText(artist_item.artist);
artist_albums.setText(String.valueOf(artist_item.albums)+"Albums");
artist_tracks.setText(String.valueOf(artist_item.tracks)+"Tracks");
ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity,R.layout.spinner_item);
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
adapter.add(default_msg);
List<Track> tracks = new ArrayList<Track>();
tracks.clear();
String[] SELECTION_ARG = {""};
SELECTION_ARG[0] = artist_item.artist;
ContentResolver resolver = activity.getContentResolver();
Cursor cursor = resolver.query( 略 );
album_hash.clear();
while( cursor.moveToNext() ){
略
}
Set<Entry<String, String>> s = album_hash.entrySet();
for (Iterator<Entry<String, String>> i = s.iterator(); i.hasNext();) { 略 }
Spinner spinner = (Spinner) partView.findViewById(R.id.album_spinner);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
省略
});
ListView trackList = (ListView) partView.findViewById(R.id.list);
track_adapter = new ListTrackAdapter(activity, tracks);
trackList.setAdapter(track_adapter);
trackList.setOnItemClickListener(activity.TrackClickListener);
trackList.setOnItemLongClickListener(activity.TrackLongClickListener);
return partView;
}
private String objectStrip(String base){
省略
}
private void setList(String item){
省略
}
}
以上 Fragmentの基本の形を 基本通りに書き換えるだけの処理に
検索とハッシュマップの作成 という仕事が割り込んでいるだけで すこし煩雑ですが複雑な点はあまりないのでゆっくり消化すれば 行けるとおもいます。