日々是好日

プログラミングについてのあれこれ、ムダ知識など

DataBinding で ViewHolder パターンを書き直してみた

ListView と DataBinding を組み合わせてガリガリ書いてましたが、RecyclerView に置き換えて使わなくなってしまったので供養のための投稿です。

完全にメモなので詳細な説明はなし……すみません(; ˘ω˘)

言語は Kotlin です。

data class 定義

ListView に表示する項目の data class を適当に定義。

data class Record(...)

xml

ListView のアイテムのレイアウトに Record クラスをバインドする。Glide 使ってるので、ImageView にカスタムセッターとしてandroid:image_url定義しています。

バインドしたら念のためリビルド。

<layout>
    <data>
        <variable name="record" type="Record" />
    </data>

    <ConstraintLayout... >

        <ImageView
                #カスタムセッター
                android:image_url="@{record.imageUrl}"
        />

        <TextView
                android:text="@{record.recordedTime}"
        />

        <TextView
                android:text="@{record.comment}"
        />
    </ConstraintLayout>
</layout>

カスタムセッターを拡張関数で定義。

@BindingAdapter("android:image_url")
fun ImageView.setImageUrl(url: String) {
    Glide.with(context).load(url).into(this)
}

Adapter 定義

Adapter クラスを定義。

ViewHolder パターンでは内部的に ViewHolder クラスを定義して findViewById でView を取得していましたが、DataBinding を利用して構築すると不要になります。

class RecordAdapter(context: Context) : ArrayAdapter<Record>(...) {
    private val inflater = ... //Inflater生成
    private lateinit var binding: RecordItemBinding //自動生成されるクラス

    override fun getView(..., convertView: View?, ...): View {
        when (convertView) {
            null -> {
                binding = DataBindingUtil.inflate(inflater, R.layout.record_item, parent, false)
                binding.root.tag = binding
            }
            else -> binding = convertView.tag as RecordItemBinding
        }

        val record = this.getItem(position)
        if (record != null){
            binding.record = record
        }

        return binding.root
    }
}

あとは普通に ListView に RecordAdapter をセットすれば大丈夫、なはず。不足してたら後で追記します。

アレだったら、ListView に@BindingAdapter("bind:adapter")等で拡張関数を定義すればもっと簡単になると思います。

では。