2011年4月9日土曜日

Android ListView positionによってViewを変える

以前に「複数のリストを表示する」ってのを書いたが、また別の方法があったので書いておく。「複数のリスト」という言い方が間違ってる気がしてきたが、やりたことは前回のときに表示したかったもの。

ListViewのViewをpositionによって変えることで、見出し行は見出しのビューで、項目の行は項目のビューを使えば見ため的には同じようには見える。ただし、プログラム的には複数のリストではなく、1つのArrayList。

今回の例での表示はこんなん。著者と作品のリスト。著者が見出しにあたる。

ListActivityを継承したメインのactivityではこういった形でリスト登録。
        novels = new ArrayList();
        novels.add(new Novel("夏目漱石", "", 0));
        novels.add(new Novel("吾輩は猫である", "漱石の処女作であると共に、・・・省略", 660));
        novels.add(new Novel("こころ", "主人公の「先生」は,・・・省略", 380));
        novels.add(new Novel("西尾維新", "", 0));
・・・省略
        novels.add(new Novel("花物語", "薬になれなきゃ毒になれ。・・・省略", 1365));
        novels.add(new Novel("支倉凍砂", "", 0));
        novels.add(new Novel("狼と香辛料", "行商人ロレンスは、・・・省略", 620));
        adapter = new NovelAdapter(this, novels);
        setListAdapter(adapter);
ArrayList<Novel>じゃなくて、ちゃんと継承使って、著者と小説とでクラスを分けた方が綺麗だが、サンプルなんで適当。理解できない人は気にしない。

あとはNovelAdapterを作ってく。まずは見出し行はisEnabledをfalseになるようにしてやる。選択とかフォクリックとかされないために。
    @Override
    public boolean isEnabled(int position) {
        return !(getItem(position).getDesc().equals(""));
    }
今回はgetDescが空文字の時に見出し行としてしまう。ちゃんと継承を使っているなら(ry

で、あとはgetView。
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (isEnabled(position)) {
            if (convertView == null
                    || convertView.getId() != R.layout.novel) {
                convertView = getNovelView();
                holder = (ViewHolder) convertView.getTag();
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            Novel novel = this.getItem(position);
            holder.Title.setText(novel.getTitle());
            holder.Desc.setText(novel.getDesc());
            holder.Price.setText(novel.getPrice() + "円");
        } else {
            if (convertView == null
                    || convertView.getId() != R.layout.author) {
                convertView = getAuthorView();
                holder = (ViewHolder) convertView.getTag();
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            Novel novel = this.getItem(position);
            holder.Title.setText(novel.getTitle());
        }
        return convertView;
    }
見出し行かどうかでまず処理を分ける。convertViewは使い回しされるので、(contentViewのIdで判断して)使い回されたのが使えないviewならviewをinflateする。これをしないと、著者用のViewに小説の概要をsetしようとしてNullPointerExceptionが起きたり、小説用のViewに著者だけセットして概要や価格は使いまわし前の値が入っていたりとおかしなことになるでしょう。NullPointerExceptionはエラー処理してなきゃアプリ止まるだろう。特に難しいこともしてない関数などは省略してあるが、そこは妄想で脳内補完。


前にやったaddViewとinflateの繰り返しよりはこっちのが綺麗だし、Viewを使い回してる分メモリ使用少ないし、前のはダメな例。あれはあれで勉強になったので個人的には良いが、まぁ、すみません。