見出し画像

複雑なリストをRecyclerView, Enumで作成する話

yenta というビジネスマッチングアプリのAndroidを作成している @muttsu_623 です。

株式会社アトラエのアドベントカレンダー 12/15の分です。
(1日公開が遅れました)

最近以下のようなリストを作成する機会がありました。

スクリーンショット 2019-12-15 23.35.26

要素① ~ ④はそれぞれレイアウトが全く違うのでそれぞれで作成しましたが、「自己紹介」「興味・関心」「職歴」「未入力の項目」に関してはレイアウトが似ているので、共通化したいなと考えました。

共有化したのは、これらの xml ファイルと、ViewHolderクラスです。

しかしながら、それぞれテキストとEditマークを表示するかどうかが違います。そのため、全く同じものとして扱うことができません。

普段であれば以下のようにします。

class Adapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val viewTypes: MutableList<ViewType> = mutableListOf()

    enum class ViewType {
        HEADER,
        ELEMENT1,
        ELEMENT2,
        ELEMENT3,
        ELEMENT4
    }

    override fun getViewCount() {
        viewTypes.clear() // 複数回呼ばれるので、clear
        viewTypes.add(ViewType.HEADER)
        ...
        viewTypes.add(ViewType.ELEMENT4)
    }

    override fun getItemViewType(position: Int): Int {
        return viewTypes[position].ordinal
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): ViewHolder {
        return when (ViewType.values()[viewType]) {
            ViewType.HEADER -> HeaderViewHolder.create(parent)
            ViewType.ELEMENT1 -> ELEMENT1ViewHolder.create(parent)
            ...
            ViewType.ELEMENT4 -> ELEMENT4ViewHolder.create(parent)
        }
    }

    override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder,
        position: Int
    ) {
    }
}

この場合、上記のテキストを変更する・Editマークの表示・非表示を分けることができません。

しかしながら、今のようにとりあえずViewTypeを増やしまくることもしたくありません。理由としては、onCreateViewHolderのところで指定する量が増えてしまうからです。(一部抜粋)

enum class ViewType {
    HEADER_INTRODUCTION,
    HEADER_WORK,
    HEADER_EDUCATION,
    ...
}

override fun getViewCount() {
    viewTypes.clear()
    viewTypes.add(ViewType.HEADER_INTRODUCTION),
    viewTypes.add(ViewType.HEADER_WORK),
    viewTypes.add(ViewType.HEADER_EDUCATION)
}

override fun getItemViewType(position: Int): Int {
    return viewTypes[position].ordinal
}

override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): ViewHolder {
    return when (ViewType.values()[viewType]) {
        ViewType.HEADER_INTRODUCTION, 
        ViewType.HEADER_WORK, 
        ViewType.HEADER_EDUCATION -> HeaderViewHolder.create(parent)
    }
}

override fun onBindViewHolder(
    holder: RecyclerView.ViewHolder,
    position: Int
) {
    when (viewTypes[position]) {
        ViewType.HEADER_INTRODUCTION -> hogehoge()
        ...
    }
}

HEADERの数が少なかったらいいが、多いとめんどくさいですね。。
また、HEADERはそれぞれtext, Editマークのvisibilityを持っているので、Enumにして共有の要素を持たせておきたいです。

このことから、HEADERもEnumで扱いたい。onCreateViewHolderでViewHolderの作成をしやすいように、もともとのViewTypeもEnumで扱いたい。

そうしたときに、Headerとそれ以外のElementを完全に別のEnumで作成するのは微妙と考えた。ので、以下のように扱うこととした。

(あくまで僕の考えなので、ここに対してはぜひコメントでフィードバックください)

interface ViewType

enum class ParentViewType {
    HEADER,
    ELEMENT1,
    ELEMENT2,
    ELEMENT3,
    ELEMENT4
}

interface ChildViewType {
    val parent: ParentViewType
}

enum class Header(val textId: val editVisibility) : ChildViewType {
    INTRODUCTION(R.id.title_intro, View.VISIBLE),
    WORK(R.id.title_work, View.GONE),
    EDUCATION(R.id.title_education, View.GONE)
    ...
}

override fun getViewCount() {
    viewTypes.clear()
    viewTypes.add(Header.INTRODUCTION),
    viewTypes.add(ParentViewType.ELEMENT1),
    viewTypes.add(Header.WORK),
    viewTypes.add(ParentViewType.ELEMENT2),
    viewTypes.add(Header.EDUCATION),
    ...
}

override fun getItemViewType(position: Int): Int {
    val viewType: ViewType = viewTypes[position]
    return if (viewType is ChildViewType) viewType.parent.ordinal
           else (viewType as ParentViewType).ordinal
}

override fun onCreateViewHolder(
    parent: ViewGroup,
    viewType: Int
): ViewHolder {
    return when (ViewType.values()[viewType]) {
        ViewType.HEADER -> HeaderViewHolder.create(parent)
    }
}

override fun onBindViewHolder(
    holder: RecyclerView.ViewHolder,
    position: Int
) {
    when (holder) {
        is HeaderViewHolder -> holder.bind(viewTypes[position] as Header)
        is Element1ViewHolder -> holder.bind(viewTypes[position])
        ...
    }
}

private fun HeaderViewHolder.bind(header: Header) {
    bindText(header.textId)
    bindEditVisibility(header.editVisibility)
}

上記のように変更することにより、onCreateViewHolder()とonBindViewHolder()ではParentViewTypeごとに処理を行うことができ、
onBindViewHolder()内のそれぞれのViewHolderクラス内で処理を分けることにより、適した処理を行うことができる。

文章だと難しいので、あとでサンプルを追加します笑

僕の記事はこんなところにします。

この記事が気に入ったらサポートをしてみませんか?