AndroidのViewの中でも頻出かつクセのある ListView関連のTips集Vol.2
カテゴリ毎にHeaderが追従するタイプのListViewである、StickyListHeadersListView
の基本的な使い方と使用上の注意点。
はじめに
使い方については以下の本家GitHubを参考にしましょう。
emilsjolander/StickyListHeaders
とはいえ、
- そんなの読んでる時間ないよ!
- 英語きっついわー。。。
- すぐにでもStickyListHeadersの世界を体感したい!
という忙しい人は以下をお読みください。
ライブラリの登録
build.gradle.xml
に以下を追加する。
dependencies {
compile 'se.emilsjolander:stickylistheaders:2.6.0'
}
これで準備は完了!
「あれっ?使えないぞ?」
という人は、落ち着きましょう。
ネットにつないで、一度BuildしてデータをDLしてくる必要があります。
(本家サイトでは、git clone
のやり方も書いてあります)
StickyListHeadersListView
の基本構造
StickyListHeadersListViewはその名の通りカスタムListViewです。
なので、ListViewの基本的な使い方に不安がある人は、まずそちらを復習しましょう!
(ここでは割愛します)
■ データBindはListViewと同じ
ListViewにデータを入れ込む代表的な方法としてAdapter経由で行うものがありますが、StickyListHeadersListViewでも同じ。
基本はStickyListHeadersAdapter
をimplementsしたAdapterを使いましょう。
■ 何が違うか -> Headerがsticky
このViewを使いたい人には自明ですが、StickyListHeadersListViewはHeader部分がカテゴリ毎に追従してきます。
ListViewではgetView
に処理を書けば大体OKですが、StickyListHeadersListViewではHeaderの動きを制御する getHeaderId
、HeaderのViewを扱う **getHeaderView
**にきちんと処理を書かないとうまく動きません。
■ Headerがstickyになる仕組み
詳細は後述しますが、getHeaderId
にて、アイテム行がどのHeaderグループに所属するのかを判定しています。
例えば、
リストPosition |
getHeaderId の返り値(※1) |
Headerグループ | Header生成(※2) |
---|---|---|---|
0 | 1 | A | ◯ |
1 | 1 | A | - |
2 | 1 | A | - |
3 | 2 | B | ◯ |
4 | 2 | B | - |
5 | 3 | C | ◯ |
6 | 4 | D | ◯ |
7 | 4 | D | - |
8 | 4 | D | - |
9 | 4 | D | - |
10 | 5 | E | ◯ |
11 | 5 | E | - |
のようになります。
getHeaderId
の返り値は連続していなくて良いですが、 long
型である必要があります。
キャストされるので、int
型あたりが返っていれば大丈夫です。
※1: 返り値は適当です(long型であればなんでも)
※2: 「-」は生成されたものが生き残るので再生成はされない
仕組み自体は簡単ですね。
つまり、Stickyに動かしたい範囲(=所属させたいHeaderグループ)毎に、同じgetHeaderId
を返すようにすればよい!!
ということです!
実装方法
■ ファイルと役割
以下のファイルを参考にしてください。
ファイル名 | 役割 |
---|---|
StickyActivity.java | RootActivity |
StickyAdapter.java | データバインド用Adapter |
main_sticky.xml | StickyActivityのレイアウト |
sticky_header_row.xml | StickyListのHeaderレイアウト |
sticky_item_row.xml | StickyListのItemレイアウト |
注意点はStickyAdapter.java
におけるgetHeaderId
の扱いです。
■ ファイル詳細
まずはRootActivityです。
public class StickyActivity extends Activity {
private StickyListHeadersListView mListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_sticky);
mListView = (StickyListHeadersListView) findViewById(R.id.sticky_listview);
}
@Override
protected void onResume() {
super.onResume();
StickyAdapter adapter = new StickyAdapter(this, android.R.layout.simple_list_item_1, createSampleArray());
mListView.setAdapter(adapter);
}
// Adapterに渡すサンプルListを生成します
private List<String> createSampleArray() {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add("position: " + i);
}
return list;
}
}
次はデータを入れ込むAdapterを見てみましょう。
注意点はこれまでお話してきた通りです。
public class StickyAdapter extends ArrayAdapter<String> implements StickyListHeadersAdapter {
private LayoutInflater mInflater;
public StickyAdapter(Context context, int resource, List<String> itemList) {
super(context, resource, itemList);
mInflater = LayoutInflater.from(context);
}
// Headerのレイアウトを定義します
@Override
public View getHeaderView(int i, View view, ViewGroup viewGroup) {
HeaderViewHolder holder;
if (view == null) {
view = mInflater.inflate(R.layout.sticky_header_row, null);
holder = new HeaderViewHolder(view);
view.setTag(holder);
} else {
holder = (HeaderViewHolder) view.getTag();
}
holder.textView.setText("Header: " +getHeaderItem(i));
return view;
}
// 【重要】Headerグループ毎に同じ値を返すようにしましょう
// ここではItemポジションを5で割った商が同じになるものをグループにします(5個/グループ)
// getHeaderItemを参考にしましょう
@Override
public long getHeaderId(int i) {
return getHeaderItem(i);
}
// getItemにならい、Header版も作る優しさが持てるといいですね
// 各アイテムのHeaderはポジションを5で割った商です
// getHeaderIdを参考にしましょう
public int getHeaderItem(int position) {
return position / 5;
}
// おなじみのgetViewです
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ItemViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.sticky_item_row, parent, false);
holder = new ItemViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ItemViewHolder) convertView.getTag();
}
// Adapterに渡されたテキストを入れます
holder.textView.setText(getItem(position));
return convertView;
}
public static class HeaderViewHolder {
TextView textView;
// コンストラクタ内でidバインドを行なうとスッキリします
public HeaderViewHolder(View view) {
textView = (TextView) view.findViewById(R.id.header_textview);
}
}
public static class ItemViewHolder {
TextView textView;
// コンストラクタ内でidバインドを行なうとスッキリします
public ItemViewHolder(View view) {
textView = (TextView) view.findViewById(R.id.item_textview);
}
}
}
お疲れ様です。 あとはレイアウト関連をちゃちゃっと片付けましょう。
<?xml version="1.0" encoding="utf-8"?>
<se.emilsjolander.stickylistheaders.StickyListHeadersListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sticky_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_light"
android:orientation="vertical">
<TextView
android:id="@+id/header_textview"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:text="test"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="56dp"
android:orientation="vertical">
<TextView
android:id="@+id/item_textview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="item"
android:textSize="16sp" />
</LinearLayout>
実装結果
以下の様な挙動ができたら成功です!
(marginなど各種数値は最低限しかセットしていません)
それではStickyな世界を楽しみましょう!
※注意
レイアウトはもっとカッコよくしましょう。
サンプルはダサくて泣けます。。笑
ListView Tips集
- 【ListView Tips Vo.1】ListViewの子要素(アイテム)のClickがおかしくなる 〜主な原因と対処法〜
- 【ListView Tips Vol.2】忙しい人のためのStickyListHeadersListViewの使い方 〜基本編〜
- 【ListView Tips Vol.3】ListViewでアイテムアニメーションしよう! 〜View.startAnimationに関する考察〜
- 【ListView Tips Vol.4-1】ButterKnifeで効率よくIDバインドする 〜findViewByIdもOnClickも、もういらない(仮)〜