補足

以下の質問に対する補足です。

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の基本の形を
基本通りに書き換えるだけの処理に

検索とハッシュマップの作成 という仕事が割り込んでいるだけで
すこし煩雑ですが複雑な点はあまりないのでゆっくり消化すれば
行けるとおもいます。

Add a comment