KotlinでData Bindingをする - ListView編

# 前提
KotlinでData Bindingを行わずListViewが表示されている状態からはじめます。
はじめの準備は[こちら](http://qiita.com/kykomi/items/104905c20242a7ded9ee)を参照してください。各種モデルクラスも記載されています。

# 準備
今回は複数個のBookインスタンスをListViewで表示します。
バインドするため、`BookShelf`クラスを作成します。

````BookShelf.kt
class BookShelf(private val books: List<Book>) {
    val count: Int = books.count()

    fun bookAt(index: Int): Book {
        return books[index]
    }
}
````

# リストアイテムのレイアウトを編集する
バインドはListViewの各アイテムについて行います。
[基本編](http://qiita.com/kykomi/items/104905c20242a7ded9ee)と同じように下記をリストアイテムのレイアウトについても行います。
ListViewの1アイテムの定義なので、バインドするクラスは1つの本を表す`Book`です。

1. ListViewのアイテム用のレイアウトファイルを作成
1. Layoutタグで括る
2. dataタグを追加する
3. バインドの定義をする

````book_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="book"
            type="com.example.kotlinsample.model.Book" />
    </data>
    <LinearLayout ...>

        <TextView
            ...
            android:text="@{book.title}"
            android:id="@+id/titleText"
            ... />

        <TextView
            android:text="@{String.valueOf(book.price)}"
            android:id="@+id/authorText"
            ... />
    </LinearLayout>
</layout>
````

# データアダプタを用意する
ポイントは、以下のとおりです。

* ListViewに表示するアイテムのレイアウトクラスに対応したBindingクラス（`BookItemBinding`クラス)が自動生成されているので、それを使う
* `getView`で`convertView`が
  * `null`の時に`BookItemBinding`クラスを作成
  * それ以外の時は使い回す
      * Bindingクラスのinflateメソッドが遅いので、使いまわさないと速度が落ちる
* `getView`の戻りは`BookItemBinding`のインスタンスの`root`することでViewが返る
* ViewModelを設定するのを忘れずに！

また、bindingはアダプターのメンバ変数として定義し、nullである場合のみインスタンス化してメンバ変数に代入する方法でも良いと思います。

````ListBindingAdapter.kt
class ListBindingAdapter(val context: Context, val bookShelf: BookShelf): BaseAdapter() {
    var inflater: LayoutInflater
    val books = bookShelf

    init {
        inflater = LayoutInflater.from(context)
    }

    override fun getCount(): Int {
        return books.count
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
        var binding: BookItemBinding?
        if (convertView == null) {
            binding = BookItemBinding.inflate(inflater, parent, false)
            binding.root.tag = binding
        } else {
            binding = convertView.tag as BookItemBinding
        }
        binding?.book = getItem(position) as Book
        return binding?.root
    }

    override fun getItem(position: Int): Any? {
        return bookShelf.bookAt(position)
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

}
````

# データアダプターをListViewに対してセットすれば完了

こんな感じで普通の場合と同じ

````ListViewSampleActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_list_binding)

        val listView = findViewById(R.id.bookShelf) as ListView

        // 適当にデータを作成
        val bookList = mutableListOf<Book>()
        for (i in 0..100) {
            bookList.add(Book(
                    title = "漫画 ($i)巻",
                    price = i,
                    authors = listOf<Author>()
            ))
        }
        val bookShelf = BookShelf(books = bookList)
        listView.adapter = ListBindingAdapter(this, bookShelf)
    }
````

# 最後に
* Bindingクラスの使い回しのベストプラクティスが不明
  * tagに入れているがイマイチ
  * メンバ変数で保持する方が良いか

そんなに通常のViewとやること変わらないですね。