本記事では KotlinでAndroidのUIを組み立てるライブラリ Ankoを使ってListViewを作成します。
ListViewと、ListViewの各アイテムのViewをAnko DSLで組み立てます。
DSLでListViewを追加
まずはAnko DSLでListViewを追加する部分は以下となります。
verticalLayout {
listView {
adapter = MyListViewAdapter(ctx, userList)
}
}
ListViewを追加し、Adapterを設定しています。
MyListViewAdapterはユーザーの一覧を表示するための自作のAdapterです。
Adapterの中でContextを利用したいので、プロパティにしてコンストラクタに渡すようにしました。
Adapter
AdapterのgetViewの部分を示します。
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インターフェースを実装した以下のようなクラスです。
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のコードとしてはちょっと不格好な気がします。
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を高速化する
AnkoComponentはインターフェースですので、以下のようなAnkoComponentを実装したカスタムビューをつくることができます。
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関数については以下の記事を参照してください。
このようなカスタムビューを以下のように使用します。
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を実装する必要はないようです。
先ほどのコードは以下のようにしても動きました。
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
}
}
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にしたい問題
あたりの話をするかもしれません。