「
アニメ番組表 API使った通信Androidアプリを作ってみた」でアニメのタイトル一覧をListViewで表示した。
でもタイトルだけ表示しても面白くないので、ListViewをカスタマイズする
ListViewを制する者は、AndroidのView表示を制す!
というだけあっていろいろなテクニックが必要だった
ListViewの作り
ListViewに文字を表示させるには
表示させたいデータをAdapterクラスに入れて、Apapterに設定してあるレイアウトを基にViewを組立て、ListViewに表示させる
既存のAdapterでリストをつくるとこうなる
- // Viewの生成に必要
- Context context = this.getApplicationContext();
-
- // Adapterの設定 Adapterのコンストラクタ(Context,LayoutのID,リストID, リストデータ)
- String[] list = { "りんご", "ゴリラ", "ラッパ", "パイナップル" };
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, android.R.layout.simple_expandable_list_item_1, list);
-
- // AdapterをListViewにセット
- ListView listView = new ListView(context);
- listView.setAdapter(adapter);
「android.R.layout.simple_expandable_list_item_1」は既存のセル用レイアウトでTextViewが1個あるだけ
ArrayAdapterのコードを見てみると、レイアウト内のTextViewに文字列を入れる仕組みになってた
オリジナルのListViewを作るにはAdapterのレイアウトを組み立てる仕組みを作ってやればいい
Layoutの定義
表示するセルのレイアウトを作ってく
| プロジェクトフォルダ/res/layout/
定義は右や上のフォルダの位置にxmlファイルつくって描く
ソースコード上でも描けるけど、この方が見やすいからねw
名前は何でもいいので、「list_aitem.xml」としておく。この名前はレイアウトのIDとして後々使用するので適当すぎると困る
|
今回は曜日、開始時間、放送局、タイトルをTextViewで表示する
こんな感じで設定
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal" >
-
- <TextView
- android:id="@+id/week_view"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_gravity="center"
- android:layout_margin="10dip"
- android:textColor="#000000"
- android:textSize="50sp" />
-
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical" >
-
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_margin="10dip"
- android:background="#0FFFF0"
- android:orientation="horizontal" >
-
- <TextView
- android:id="@+id/time_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="#000000"
- android:textSize="20sp" />
-
- <TextView
- android:id="@+id/tv_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="5dip"
- android:textColor="#000000"
- android:textSize="20sp" />
- </LinearLayout>
-
- <TextView
- android:id="@+id/title_view"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:singleLine="true"
- android:ellipsize="end"
- android:textColor="#000000"
- android:textSize="30sp" />
- </LinearLayout>
-
- </LinearLayout>
アニメタイトルのTextViewが問題
TextViewは列をはみ出そうとすると自動で改行を入れてくれる。しかし、ListViewでこれをやられると各セルの高さがばらばらになる
個人的にセルの高さは統一したいので、TextViewは一行で表示、はみだした分を…となるように設定する
色つけてる箇所がポイントで、
singLineを"true"で一行表記設定
ellipsize="end"で…の位置を設定
ちなみにellipsize属性に設定できる他のフラグはこんな感じ
フラグ | 効果 | 例 |
end | 文末が… | アニメタイ… |
start | 文頭が… | …メタイトル |
middle | 文の途中が… | アニメ…トル |
none | …をしない | アニメタイト |
Adapter作り
上記コードでは既存のAdapterを使っているが、今回は自作したレイアウトにデータを詰め込む実装がいるのでBaseAdapterを継承してオリジナルAdapterを作っていく
データクラスとしてAnime.javaを作っとく。
とりあえずAnimeAPIから得られる情報一式をメンバ変数にする
- class Anime {
- public String title, url, time, station, state, next, episode, cable, today, week;
- }
BaseAdapterは抽象クラスで実装しないといけないクラスがいくつかある
この辺はだいたいテンプレ。特殊なことしないならこれでOK
- /**
- * アニメ表示用Adapter.
- *
- */
- public class AnimeAdapter extends BaseAdapter {
- private static final int LAYOUT_ID = R.layout.list_aitem; // 上記で定義したレイアウトのID
- private Anime[] list; // データリスト
- private Context mContext; // レイアウトIDからLayoutインスタンス取得に必要
-
- /** コンストラクタ. */
- public AnimeAdapter(final Context context, Anime[] animes) {
- this.mContext = context;
- this.list = animes;
- }
-
- @Override
- /** リストの数を取得 */
- public int getCount() {
- return list.length;
- }
-
- @Override
- /** リストから特定のAnimeを取得.戻り値Anime型にしとくのがミソ */
- public Anime getItem(int position) {
- return list[position];
- }
-
- @Override
- /** リストのID取得(まぁリストの順番でOK) */
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- /** レイアウトにデータを設定する(一番したい処理) */
- public View getView(int position, View convertView, ViewGroup parent) {
- //TODO 色々テクニックいるので下で書く
- return null;
- }
- }
次に一番やりたかった処理。レイアウトにデータを詰めて表示する処理
- セル用レイアウトにコンバート
セル用レイアウトにデータを突っ込む処理
ListViewは生成されたとき全てのセルを作ってるわけではない。セルのViewは画面に出たときに生成されてる
そして、スクロールして新しいセルが表示されたとき、このメソッド(# getView())は呼ばれViewを生成する
引数は
・position | ⇒ | 表示すべきリストのポジション |
・convertView | ⇒ | ListViewで表示するセル用レイアウトのインスタンス |
・parent | ⇒ | よくわからん(今回使わんので無視) |
最初convertViewはnullになっていて、実装上で設定する必要がある
そこで必要なのがレイアウトのインスタンスを取得するLayoutInflaterクラス。システムからLayoutInflaterのインスタンスを呼び出し、# inflateメソッドでレイアウトを指定してレイアウトのViewのインスタンスを取得する
- LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- convertView = inflater.inflate(R.layout.list_aitem, null);
そのレイアウトから各Viewのインスタンスを取得するには#findViewByIdメソッドをつかう
XMLで定義したIDを指定してViewのインスタンスを得て設定する
- TextView textView = (TextView) convertView.findViewById(R.id.title_view);
以上を行い、レイアウトを設定して戻り値に修正後のレイアウトを指定すればOK
- @Override
- /** レイアウトにデータを設定する(一番したい処理) */
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- LayoutInflater inflater = (LayoutInflater) this.mContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- convertView = inflater.inflate(R.layout.list_aitem, null);
- }
- Anime anime = getItem(position);
- ((TextView) convertView.findViewById(R.id.title_view)).setText(anime.title);
- ((TextView) convertView.findViewById(R.id.time_view)).setText(anime.time);
- ((TextView) convertView.findViewById(R.id.week_view)).setText(anime.week.substring(0, 1));
- ((TextView) convertView.findViewById(R.id.tv_view)).setText(anime.station);
- return convertView;
- }
- 高速処理
上記の方法でも処理できるが、毎回findViewById()をするとViewの呼び出し時間がかかってしまう難点がある
それを回避するためにViewHolderという考え方を使う。レイアウトを使いまわす手法
セル内のそれぞれのViewの参照値あらかじめ持たせておき、そこのインスタンスにデータを突っ込む
- @Override
- /** レイアウトにデータを設定する(一番したい処理) */
- public View getView(int position, View convertView, ViewGroup parent) {
- View v = convertView; // レイアウト
- ViewHolder holder; // ホルダー
- if (v == null) {
- LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- v = inflater.inflate(R.layout.list_aitem, null);
-
- // ホルダーを設定する
- holder = new ViewHolder();
- holder.titleView = (TextView)v.findViewById(R.id.title_view);
- holder.timeView = (TextView)v.findViewById(R.id.time_view);
- holder.weekView = (TextView)v.findViewById(R.id.week_view);
- holder.tvView = (TextView)v.findViewById(R.id.tv_view);
- v.setTag(holder);
- }else{
- // レイアウトからViewフォルダーを取得
- holder = (ViewHolder)v.getTag();
- }
- Anime anime = getItem(position);
- // Viewフォルダに情報詰め込む
- holder.titleView.setText(anime.title);
- holder.timeView.setText(anime.time);
- holder.weekView.setText(anime.week.substring(0,1));
- holder.weekView.setBackgroundColor(color.get(anime.week));
- holder.tvView.setText(anime.station);
-
- return v;
- }
- // ホルダークラス
- class ViewHolder {
- TextView titleView, timeView, weekView, tvView;
- }
これでOK
完成!