Android
Kotlin
DSL
RecyclerView

Kotlinで簡単なRecyclerViewを量産できるようにDSLを作る

モチベーション

RecyclerViewを最初から実装するときなんだかんだ言って面倒ですよね。
しかも簡単なものでさえ。(慣れもありそうですが)

極力小さい実装で動作確認したい!って時にも、一からってなると、ちょっと忘れていたりして、検索するとJavaで書かれていたり、単純にコードの行数が多かったり、ViewHolder、Adapterの実装、クラスの命名・・・やりたいこととのギャップが多くて、意外とストレスなんですよね。

僕も簡単なサンプルアプリをつくって、いろんなバリエーションのサンプルの実装を紹介したいって時がたまにあって、プロジェクト作成して、Androidxにマイグレーションして、そして毎回ここで、えーつらい・・・ってなる。(そして別のものに気がいく)

なのでそう言う労力減らしたいなーと思って簡単なコードで実装できるようにしてみました。

完成品

以下は毎回作るのに必要になるコードです。

MainActivity.kt
package com.exmample.sampleapp

import android.app.Activity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.item_topic.view.*
// ここに今回のキモになる実装を入れてある
import com.exmample.shared.widget.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initLayout()
    }
}
// ネスト減らしたいって願望だけで拡張関数(ついでにActivityがもつ状態からも独立)
fun Activity.initLayout() {
    data class Column(val title: String, val description: String, val screen: Screen)
    val list = listOf(
        // Screenがタップされたときの遷移先を表す
        Column("Topic 01", "これはトピック1です", Screen.TOPIC01),
        Column("Topic 02", "これはトピック2です", Screen.TOPIC02),
        Column("Topic 03", "これはトピック3です", Screen.TOPIC03)
    )
    // レイアウトファイルに指定したRecyclerViewのidからDSLの実装が生える!!
    topic_list(linerLayoutManager()) {
        adapter(R.layout.item_topic) {
            onBind { vh, pos ->
                list[pos].let { (ttl, dsc, dest) ->
                    vh.itemView.title.text = ttl
                    vh.itemView.description.text = dsc
                    // 画面遷移部分は別途いい感じに実装してください
                    vh.itemView.setOnClickListener { goNextScreen(dest) }
                }
            }
            itemCount = { list.size }
        }
    }
}

画面はこんな感じ。

どうですか?これならある程度簡単なモックならサクッと作れそうにみえないですかね?

どうやってつくったか?

もうすでにKotlinをつかったDSLの実装について理解しておられるかたは、飛ばしてください。

別に特別なライブラリをつくったわけではないです。Kotlinのラムダ式をつかってシンプルなRecyclerViewのためのDSL(ドメイン特化言語)と言うものを作りました。

KotlinでのDSLの実装Tipsについては別の人がいろいろやっているので割愛しますが、公式のリンクくらいはのっけておきますね。
はいこれ→ https://kotlinlang.org/docs/reference/type-safe-builders.html

これをみて勉強して、HTMLのビルドと同じ要領でできるんだと理解できればいいです。

RecyclerViewのDSL実現するためにつくったもの

多少長くなりますが、70行いかないくらいです。

RecyclerView
package com.exmample.sampleapp.shared.widget

import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView


typealias OnCreateViewHolder<VH> = (parent: ViewGroup, viewType: Int) -> VH
typealias GetItemCount = () -> Int
typealias OnBindViewHolder<VH> = (holder: VH, position: Int) -> Unit

object SimpleRecyclerView {
    class Adapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        lateinit var makeViewHolder: OnCreateViewHolder<out RecyclerView.ViewHolder>
        lateinit var itemCount: GetItemCount
        lateinit var onBindListener: OnBindViewHolder<in RecyclerView.ViewHolder>
        override fun onCreateViewHolder(p: ViewGroup, vt: Int): RecyclerView.ViewHolder = makeViewHolder(p, vt)
        override fun getItemCount(): Int = itemCount()
        override fun onBindViewHolder(vh: RecyclerView.ViewHolder, pos: Int) = onBindListener(vh, pos)
    }
}

data class VH(val itemView: View) : RecyclerView.ViewHolder(itemView)

fun Activity.linerLayoutManager() = LinearLayoutManager(this)

// こいつのインポートが若干だるいです(多分)
operator fun RecyclerView.invoke(
    loMgr: RecyclerView.LayoutManager,
    f: RecyclerView.() -> Unit
) {
    layoutManager = loMgr; f()
}

fun RecyclerView.adapter(
    @LayoutRes layoutResId: Int,
    initAdapter: SimpleRecyclerView.Adapter.() -> Unit
) {
    this.adapter = SimpleRecyclerView.Adapter().apply {
        initAdapter()
        viewHolder(layoutResId)
    }
}

fun SimpleRecyclerView.Adapter.viewHolder(
    @LayoutRes layoutResId: Int
) {
    makeViewHolder =  { parent: ViewGroup, _: Int -> 
VH(parent.context.inflate(layoutResId, parent))
    }
}

fun SimpleRecyclerView.Adapter.onBind(
    onBind: (viewHolder: RecyclerView.ViewHolder, position: Int) -> Unit
) {
    onBindListener = onBind
}

fun Context.inflate(@LayoutRes layoutResId: Int, parent: ViewGroup, attachToRoot: Boolean = false): View =
    LayoutInflater.from(this).inflate(layoutResId, parent, attachToRoot)

こんな感じで関数の引数に
f: Adapter.()->Unit
みたいなラムダ式の定義のレシーバー(上のAdapterの部分)がわかれば、こんな感じのものを作るのに迷うことが減るとおもいます。

さあ、これにあとはレイアウトファイルを用意してあげて、画面遷移の実装をするだけで簡単なものは作れるでしょう!

もっとよいライブラリあれば教えてください

僕がしらないだけかもしれないので、こんな感じにレイアウトファイルさえつくっておけばサクッとRecyclerViewを作れるみたいなライブラリがあればおしえてもらえたら嬉しいですー。ちょっと似ていて、マージンとか設定するやつはあるんですけど、いやそれやないねん・・・みたいなものが多いので。
あと面白そうなカスタマイズアイデアや、ツッコミがあれば気兼ねなくコメントしていってくださいね。みんなで気楽にAndroidアプリつくれるようにしましょう!!!

以上。駄コード失礼いたしました。