2010年12月22日水曜日

ListViewで最後尾までスクロールしたら自動的に要素を追加読み込みするサンプル

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

概要

Android Marketみたく、ListViewの最後尾に来たらクルクル出して要素追加したいなーと思ったのでやってみました。
内容的には、文字のリストを10個表示し、最後尾までスクロールすると10個さらに追加していく感じです。

ファイル構成

ファイル構成としては以下の様な感じです。
・src
    jp.dip.sys1.android.listview_additional_reading.ListViewAdditionalReading.java

・res
    layout
        listview_footer.xml
        main.xml
他は普通のAndroidプロジェクトと同じです。

ListView最後尾のクルクルはListView#addFooterView(View)で。

ListViewにはaddFooterView(View)なるメソッドがあり、そこに任意のViewを突っ込むと、
ListViewの要素の最後尾に常に表示されるViewを設定する事が出来ます。
という事で、クルクルするViewをListViewにまず突っ込みます。

■listview_footer.xml
単純にProgressBarを真ん中に表示するだけのViewです。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:textAppearance="?android:attr/textAppearanceLarge"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center">
<ProgressBar android:id="@android:id/text1"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" />
</LinearLayout>

■ListViewAdditionalReading#onCreate(Bundle)
onCreateの中でListViewのaddFooterViewしときます。多分タイミングはいつでもいいです。多分。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView listView = getListView();
listView.addFooterView(getFooter());
listView.setAdapter(getAdapter());
listView.setOnScrollListener(this);
}

■ListViewAdditionalReading#getFooter()
getFooter()の中でlistview_footerをinflateしてます。
private View getFooter() {
if (mFooter == null) {
mFooter = getLayoutInflater().inflate(R.layout.listview_footer,
null);
}
return mFooter;
}

クルクルは以下の様な感じになります。

ListView最後尾までスクロールしたよね判定はOnScrollListenerで。

フッタは勝手に出るので良いとして、あとは最後尾までスクロールしたという判定が必要です。
そこでandroid.widget.AbsListView.OnScrollListenerを利用しました。

■OnScrollListener#onScroll(AbsListView, int, int, int)
onScrollにはスクロールしているListViewと、表示されている要素の先頭インデックス、要素の表示数、要素の総数が渡ってきます。
「表示されている要素の先頭インデックス」と「要素の表示数」を足した値が「要素の総数」となる時、
ListViewの最後尾まで来た、と判定できそうです。赤字の所で判定を行い、追加読み込みの処理を呼び出すようにしました。

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (totalItemCount == firstVisibleItem + visibleItemCount) {
additionalReading();
}
}

追加読み込みしたらListView#invalidateViews()で再描画。

■ListViewAdditionalReading#additionalReading
ListViewに要素を追加させる処理です。
AsyncTaskを使っていますが、doInBackgroundでランダムでsleepさせてるだけです。通信処理等が必要な場合はdoInBackground()でメインの処理を行うといいでしょう。
onPostExecute()でListViewの要素追加、そしてListView#invalidateViews()を呼び出しています。
ListViewはデータを表示させる為にAdapterをsetします。AdapterはデータのListを持っています。
要素を追加する場合はListにデータ追加してListView#invalidateViews()を呼び出してあげます。すると表示に反映されます。
private void additionalReading() {
// 読み込み回数が最大値以上ならスキップ。フッタを消す
if (mCount >= MAX_COUNT) {
invisibleFooter();
return;
}
// 既に読み込み中ならスキップ
if (mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING) {
return;
}
mTask = new AsyncTask<Long, Void, Void>() {
@Override
protected Void doInBackground(Long[] params) {
try {
Log.d(TAG, "sleep..." + params[0]);
Thread.sleep(params[0]);
} catch (Exception e) {
e.printStackTrace();
}
return null;
};

protected void onPostExecute(Void result) {
addListData();
getListView().invalidateViews();
};
}.execute(Math.abs(new Random(System.currentTimeMillis()).nextLong() % 3000));
}

■ListViewAdditionalReading#invisibleFooter()
追加読み込み回数がMAXになるとフッタを消してます。
private void invisibleFooter() {
getListView().removeFooterView(getFooter());
}
こんな感じになります。



詳細はプロジェクトのソースを参照して下さい。
サンプルプロジェクトは下記リンクのListViewAdditionalReading.zipです。

4 件のコメント:

  1. お世話になります。

    配列を以下のように変更すると
    { "A", "B", "C", "D", "E", "F","G", "H", "I", "J" };
    →{ "A" };

    A0-A4までリスト表示され、footerが表示されたまま入力も受け付けなくなりました。
    いろいろ試したのですが解決できませんでした。
    footerを消すにはどうしたら良いのでしょうか?

    返信削除
  2. リストの個数が少ないとスクロールが起きない→onScrollも起きないと思われるので、最初に表示したときにFooterが表示されているかの判定処理も必要かと思います。

    返信削除
  3. やりたいことにドンピシャでとても役に立ちました。ありがとうございました。

    返信削除
  4. 参考にさせて頂きます!ありがとうございます!

    返信削除