概要
BaseAdapter で ViewHolder を実装する方法について述べます。主に Spinner を扱う際に役立ちます。
なぜやるのか?
Spinner と BaseAdapter を組み合わせた実装だと、LayoutInfrater.inflate する行で Android Studio の警告が出ます。
Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling.
別にこれを放置してもビルドはできますし、Spinner でスクロールのパフォーマンスが致命的になるほど要素を詰め込むことはそうそうないので、直さなくても構わないのではないかと思ってしまいがちです。実際私もずっと放置していました。
(ちなみに、リンク先を開くと RecyclerView のドキュメント が出てきます)
……が、昨今では技術者の評価に GitHub のリポジトリーを見ることも増えてきているようです。こうした雑なコードを残しておくのは自身の今後に悪い影響を与えかねないため、直しておくべきでしょう。
というのは冗談です。ViewHolder パターン自体は普遍的なデザインパターンですので、覚えておいて損はしません。私も RecyclerView を使い始めるまでは何度か書いていました。もう数年前になるので完全に忘れていましたが、せっかくなので思い出します。
BaseAdapterで画像とテキストをListView表示
実装
1. ViewHolder クラスを定義
各 View を持つクラスを定義します。これは Kotlin だと簡単です。Item を受け取って属性を各 View に当てる関数もこのクラスに実装させると良いでしょう。
class ViewHolder(private val icon: ImageView, private val text: TextView) {
fun bindItem(item: Item) {
icon.setImageDrawable(AppCompatResources.getDrawable(context, item.iconId))
text.setText(item.id)
}
}
2. getView で convertView の状態による分岐を書く
2-1 convertView が null の時
以下の 1-6 の処理をします。
- Item を入れる Layout を inflate
- 要素 View を find
- ViewHolder を初期化
- view の tag に ViewHolder のインスタンスをセット
- viewHolder に item の属性を設定
- inflate した Layout を return
if (convertView == null) {
val view = inflater.inflate(LAYOUT_ID, parent, false)
val icon = view.findViewById<ImageView>(R.id.image)
val text = view.findViewById<TextView>(R.id.text)
val viewHolder = ViewHolder(icon, text)
view.tag = viewHolder
viewHolder.bindItem(item)
return view
}
2-2 convertView が null でない時
Layout の inflate は不要なので、 tag にセットした ViewHolder を取り出して item の属性を View に当てればよいです。
val viewHolder = (convertView.tag as ViewHolder)
viewHolder.bindItem(item)
return convertView
全体のコード
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val item = items[position]
if (convertView == null) {
val view = inflater.inflate(LAYOUT_ID, parent, false)
val icon = view.findViewById<ImageView>(R.id.image)
val text = view.findViewById<TextView>(R.id.text)
val viewHolder = ViewHolder(icon, text)
view.tag = viewHolder
viewHolder.bindItem(item)
return view
}
val viewHolder = (convertView.tag as ViewHolder)
viewHolder.bindItem(item)
return convertView
}
おわりに
Spinner を扱う際に知っておくと有益な ViewHolder パターンの実装について紹介しました。Kotlin だと大分書きやすくなったという印象を受けました。そもそも Spinner 自体そんなに使わないかもしれないですが、忘れた時にこのドキュメントが役立つとちょっとうれしいです。