LoginSignup
34
31

More than 5 years have passed since last update.

DiffUtilを使ってRecyclerView.Adapterをいい感じにする

Posted at

とある勉強会でコードがほしいと言われたので書いてみます。

DiffUtil

24.2.0で追加された、2つのリストの差分計算を行うユーティリティーです。
計算量がO(N)、移動検知ありだとO(N^2)です。

calculateDiffの結果をdispatchUpdatesTo(adapter)とすることで、RecyclerView.Adapterの各種notify系が呼ばれ、いい感じにアニメーションなどをやってくれます。

使用方法はドキュメントを参照してください。

Diffable

DiffUtil.calculateDiffにはDiffUtil.Callbackが必要です。
これをAdapterごとに実装するのは面倒なので、比較可能なアイテムを表すインターフェースとCallbackの実装クラスを定義します。
好みは別れると思いますが、ついでにグローバルスコープ関数も作ります。

Diff.kt
interface Diffable {
    // otherと同じIDを持つかどうか
    fun isTheSame(other: Diffable): Boolean = equals(other)
    // otherと完全一致するかどうか
    fun isContentsTheSame(other: Diffable): Boolean = equals(other)
}

private class Callback(
        val old: List<Diffable>,
        val new: List<Diffable>
) : DiffUtil.Callback() {

    override fun getOldListSize() = old.size
    override fun getNewListSize() = new.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return old[oldItemPosition].isTheSame(new[newItemPosition])
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return old[oldItemPosition].isContentsTheSame(new[newItemPosition])
    }
}

fun calculateDiff(
        old: List<Diffable>,
        new: List<Diffable>,
        detectMoves: Boolean = false
): DiffUtil.DiffResult {
    return DiffUtil.calculateDiff(Callback(old, new), detectMoves)
}

Delegates.observableと組み合わせる

差分計算するには、新旧のListが必要になりますが、KotlinにはDelegates.observableがあるので、これを使います。

class MyAdapter : RecyclerView.Adapter<AbstractViewHolder>() {
    var items: List<Item> by Delegates.observable(emptyList()) { _, old, new ->
        calculateDiff(old, new).dispatchUpdatesTo(this)
    }
}

上記のようにAdaptervar items: List<Item>プロパティを作ります。
MyAdapter.itemsに値をセットすると、自動でDiffの反映までやってくれます。
ViewHolder周りは省略します。

実装例

ItemにはDiffableを実装します。
デフォルト実装があるので、isTheSameのみオーバーライドします。

Item.kt
data class Item(val id: Long, val fullName: String) : Diffable {
    override fun isTheSame(other: Diffable) = id == (other as? Item)?.id
}

続いてActivityなどの実装例です。
細かいのは省略してます。

val adapter = MyAdapter()
recyclerView.adapter = adapter
adapter.items = listOf(Item(0, "Item: 0"))

recyclerView.postDelayed({ adapter.items += Item(1, "Item: 1") }, 1000)

上記コードだと、最初はItem: 0のみ表示されているが、1秒後にItem: 1がアニメーションとともに表示されます。

もう少し実用的なサンプル

こちらです。
ReduxでGitHubのリポジトリを表示するだけのものです。

34
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
31