AnkoでListViewを作る

本記事では KotlinでAndroidのUIを組み立てるライブラリ Ankoを使ってListViewを作成します。
ListViewと、ListViewの各アイテムのViewをAnko DSLで組み立てます。

# DSLでListViewを追加

まずはAnko DSLでListViewを追加する部分は以下となります。

```kotlin:Anko
verticalLayout {
    listView {
        adapter = MyListViewAdapter(ctx, userList)
    }
}
```

ListViewを追加し、Adapterを設定しています。
MyListViewAdapterはユーザーの一覧を表示するための自作のAdapterです。
Adapterの中でContextを利用したいので、プロパティにしてコンストラクタに渡すようにしました。

# Adapter

AdapterのgetViewの部分を示します。

```kotlin:MyListViewAdapter
class MyListViewAdapter(var context: Context, var userList: UserList? = null) : BaseAdapter() {

    // 省略

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
        var newConvertView: View?
        var ui: MyListItemUI

        if (convertView == null) {
            ui = MyListItemUI(position)
            newConvertView = ui.createView(context.UI {  })
            newConvertView.tag = ui
        } else {
            newConvertView = convertView
            ui = newConvertView.tag as MyListItemUI
            ui.update(position)
        }

        newConvertView.tag = ui

        return newConvertView
    }
}
```

convertViewがnullのときにはMyListItemUI(後述)のインスタンスを作成し、createView()で各アイテムのViewを作成しています。
createView()には先ほどAdapterに渡したContextの拡張関数 UI { } の戻り値を渡します。
そしてViewのtagにMyListItemUIを設定します。

すでにViewが作成されている場合には、ViewのtagからMyListItemUIを取得し、Viewの更新を行います。

# AnkoComponent

MyListItemUIはAnkoComponentインターフェースを実装した以下のようなクラスです。

```kotlin:MyListItemUI
class MyListItemUI(var position: Int) : AnkoComponent<Context> {

    private var label: TextView? = null

    override fun createView(ui: AnkoContext<Context>) = with(ui) {
        verticalLayout {
            label = textView {
                text = "List item: " + position
            }
        }
    }

    fun update(position: Int) {
        label?.text = "List item: " + position
    }
}
```

**AnkoComponentを実装したクラスがViewHolderのような役割をしている**ことがわかると思います。

# AnkoComponentの実装クラスをカスタムビューにする

さてここで終わっても良いのですが、先ほどのAdapterのgetView()の部分、Kotlinのコードとしてはちょっと不格好な気がします。

```kotlin:MyListViewAdapter
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
    var newConvertView: View?
    var ui: MyListItemUI

    if (convertView == null) {
        ui = MyListItemUI(position)
        newConvertView = ui.createView(context.UI {  })
        newConvertView.tag = ui
    } else {
        newConvertView = convertView
        ui = newConvertView.tag as MyListItemUI
        ui.update(position)
    }

    newConvertView.tag = ui

    return newConvertView
}
```

もう少しすっきりさせられないものでしょうか?

ListViewを使う際にはViewHolderを使わずにカスタムビューを使用することでコードをシンプルにできる場合があります。これに関しては以下の記事を参考にしました。

[ViewHolderを使わないでListViewを高速化する](http://qiita.com/mofumofu3n/items/28f8be64d39b20e69552)

AnkoComponentはインターフェースですので、以下のようなAnkoComponentを実装したカスタムビューをつくることができます。

```kotlin:MyListItemUI
class MyListItemUI(context: Context) : FrameLayout(context), AnkoComponent<Context> {

    private var label: TextView? = null

    override fun createView(ui: AnkoContext<Context>) = with(ui) {
        verticalLayout {
            label = textView {

            }
        }.apply { this@MyListItemUI.addView(this) }
    }

    fun update(position: Int) {
        label?.text = "List item: " + position
    }
}
```

作成したUIを`apply { this@MyListItemUI.addView(this) }`の部分でカスタムビュー自身に追加しています。
apply関数については以下の記事を参照してください。

[Kotlin スコープ関数 用途まとめ](http://qiita.com/ngsw_taro/items/d29e3080d9fc8a38691e)

このようなカスタムビューを以下のように使用します。

```kotlin:MyListViewAdapter
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View =
    ((convertView as? MyListItemUI) ?: MyListItemUI(context).apply {
        // convertViewが nullの時には Viewを作成
        createView(context.UI {})
    }).apply {
        // Viewを更新
        update(position)
    }
```

非常にすっきりしました。やったね。

***

さて、このようにして作ったMyListItemUIですが、実は今のところPreviewが表示されない状態です。
プラグインのバグなのかコードの側に何らかの変更を加えれば表示されるものなのかは調査しきれていません。

実のところ、AnkoComponentインターフェースはPreviewのためにあるもので、Viewを作成するためだけならわざわざAnkoComponentを実装する必要はないようです。

[Anko Preview Plugin](https://github.com/Kotlin/anko/blob/master/doc/README.md#anko-preview-plugin)

先ほどのコードは以下のようにしても動きました。

```kotlin:MyListItemUI
class MyListItemUI(context: Context) : FrameLayout(context) {

    private var label: TextView? = null

    init {
        context.UI {
            verticalLayout {
                label = textView {

                }
            }.apply { this@MyListItemUI.addView(this) }
        }
    }

    fun update(position: Int) {
        label?.text = "List item: " + position
    }
}
```

```kotlin:MyListViewAdapter
override fun getView(
        position: Int, convertView: View?, parent: ViewGroup?): View =
        ((convertView as? MyListItemUI) ?: MyListItemUI(context)).apply {
            // Viewを更新
            update(position)
        }
```

***

次回は

- Ankoと Data Binding 同時に使えない問題
- `var label: TextView? = null` の部分に本当はnullを入れたくない問題
- `var label: TextView? = null` の部分をvarじゃなくてvalにしたい問題

あたりの話をするかもしれません。