「
アニメ番組表 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
完成!