Android ListView - 開発Tips

こんにちは。

Android AppのListViewを使って開発していろいろTipsがあったので、備忘録を兼ねてブログを書きます。

参考にしたのはこちらです。
http://blog.tappli.com/article/36222638.html
http://techbooster.jpn.org/andriod/ui/2979/
http://yaino.com/gzudoc2/0001/000108.html
http://d.hatena.ne.jp/poyonshot/20110224/1298556195
http://blog2.kojin.biz/2010/02/24/android%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E3%81%A7%E3%81%AE%E6%82%A9%E3%81%BF%E4%BA%8B%E3%81%9D%E3%81%AE%EF%BC%91/
http://www.techmaru.net/wordpress/20101123/listviewmemposition/
http://d.hatena.ne.jp/yokkong/20110426/1303805401
http://aki-ino.blogspot.com/2010/05/listview_317.html

それでは早速書いてみます。

  • ListView#addFooterView/ListView#addHeaderViewとListView#getAdapterの使い方には注意が必要でした。

まずは、今回はListView#addFooterViewを使いました。
そこで、ListViewをfindViewByIdして取得したら、ListView#addFooterViewを呼び出すようにしたほうがいいと思いました。
ListView#setAdapterを呼び出す前に実行しないといけないなら、このタイミングでいいのではと思いました。

また、ListView#getAdapterですが、ListView#addFooterView/ListView#addHeaderViewを呼び出すと、返却されるインスタンスが、HeaderViewListAdapterになります。
本来ほしいのは、ArrayAdapterや開発したArrayAdapterの子クラスですよね。
ということで、HeaderViewListAdapter#getWrappedAdapterで目的のAdapterインスタンスを取得しましょう。

なお、フッターのVisibilityや表示内容を制御する場合は、ListView#getCountに気をつけましょう。
ヘッダー、フッターの分も数えて制御しないといけません。
※Item10個をAdapter#addしてもListView#addFooterViewを実行しているとListView#getCountは11を返します。

  • ArrayAdapter#addのANR対策は取得するリストの数を分けてHandlerで呼び出しました。

これは100ループしてaddするのではなく、10ループ単位にわけて、Handlerで呼び出しました。
その際の、取得する位置はView#setTagで保持するか、Activityのインスタンスフィールドで保持しましょう。

また、自動読み込みのタイミングは、OnScrollListener#onScrollStateChangedでハンドリングしました。
スクロールがとまったらOnScrollListener.SCROLL_STATE_IDLEの状態になるので、そのタイミングで、次のリスト取得を実行しました。
このタイミングで10件ずつなら、違和感なくリストデータの取得ができると思います。

ちなみに、FooterViewの状態管理(もっと見ると読み込み中)の制御は自前で普通にやりましょう。

  • ListView#setOnItemClickListenerを使いませんでした。

インターネットを検索してもよく、ItemClickイベントが発生しない事例が多いようです。
原因は、Itemの子ViewにClickイベントが発生しているためだとあり、android:focusableやandroid:focusableInTouchModeの値をfalseにすればとありますが、それでも解決できなかったです。
なので、ListViewのItemそれぞれにOnClickListenerを設定しました。
設定するタイミングは、ListView#getViewです。
その際は、該当Viewにpositionなどを保持しておいて、ハンドリングしましょう。

  • 画像のキャッシュやAsyncタスクのキャッシュなどは、finishしたときに、clearとcancelしました。

これはOutOfMemory対策です。
ちなみに画像のキャッシュとAsyncタスクのキャッシュが必要な理由は、ListView#getViewの呼び出し回数が多すぎる!
普通に、画面内に4つのリストアイテムを表示するのに、同じpositionのアイテムが4回ぐらいListView#getViewが呼ばれます。
すでに、Taskが起動している場合は、また新規でTaskを生成して、呼び出すのは非効率です。
URLをキーにTask管理が必要だと思います。
また、画像をすでに取得済みの場合は、再利用するキャッシュの仕組みをしましょう。
こちらの場合もURLをキーに画像管理が必要だと思います。

  • 自前のスクロール位置制御は制御できませんでした。

こちらもインターネット検索をみるとArrayAdapter#notifyDataSetChangedの呼び出しとListView#invalidateViewsの呼び出しをすればOKとありますが、これが有効になるのは、ArrayAdapter#add、ArrayAdapter#removeがあったときのように思います。
単純にKeyEventに対して、ListView#setSelectionFromTopやListView#setSelectionを呼び出して、ArrayAdapter#notifyDataSetChangedを呼び出しても、変化がないです。
この辺は、もっと調査が必要かもしれませんが、現時点の回答です。

  • ListView#getChildAtとListView#getItemAtPositionがnullになる。

本来あるはずのpositionを指定して、ListView#getChildAtやListView#getItemAtPositionを呼び出しても、高速にスクロールを繰り返していると、nullが返却されることがあります。
なので、その際は、必ずnullチェックをして、Viewを利用しましょう。

  • Scrollの時に色が変わってしまうのを防ぐ

単純に好みの問題ですが、嫌ならandroid:scrollingCache="false"やListView#setScrollingCacheEnabled(false)を呼び出しましょう。

  • ListViewの行の間の区切り線を変える。

android:dividerとandroid:dividerHeightで変更可能です。

  • 最後に。

TODOは近いうちにチャレンジする機会があればやりたいですね。

  1. 自前のスクロール位置制御を完璧にしたい。