7
9

More than 1 year has passed since last update.

RecyclerViewにドラッグドロップで並べ替え機能を追加する

Last updated at Posted at 2022-12-18

あんまり使うことがなくて、使おうとする度にどうやるんだっけ?と調べるやつ

RecyclerViewはその内部でドラッグドロップで並べ替えをする機能を実装するのは非常に簡単です。

まずは、シンプルなリストを作ってみましょう

private class ItemViewHolder(val binding: ItemViewBinding) : ViewHolder(binding.root)
private class ItemAdapter(context: Context) :
    ListAdapter<Int, ItemViewHolder>(object : DiffUtil.ItemCallback<Int>() {
        override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean = oldItem == newItem
        override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean =
            oldItem == newItem
    }) {
    private val inflater = LayoutInflater.from(context)
    private val list: MutableList<Int> = MutableList(10) { it + 1 }

    init {
        submitList(list.toList())
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder =
        ItemViewHolder(ItemViewBinding.inflate(inflater, parent, false))

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        holder.binding.text.text = "item: " + getItem(position).toString()
    }
}

レイアウトとかもシンプルなものなので省略、これで以下のようなリストを作ることができました。

次に、上記Adapterに項目の入れ替え、削除を行うメソッドを追加します。Adapterに実装する必要はないですが、あくまで説明用のサンプルとしてです。

fun swap(from: Int, to: Int) {
    list.add(to, list.removeAt(from))
    submitList(list.toList())
}

fun remove(target: Int) {
    list.removeAt(target)
    submitList(list.toList())
}

LinearLayoutManagerで縦一列に並べている場合、通常onMoveは前後に動く度にコールされるので単純にfrom/toでswapしても同じですが、単純にfrom/toを入れ替えるのではなく、fromからtoの間を順次移動させてtoにfromの項目を入れる用にしています。

上記メソッドを呼び出すようにItemTouchHelperを実装します。

val helper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
    ItemTouchHelper.UP or ItemTouchHelper.DOWN,
    ItemTouchHelper.START or ItemTouchHelper.END
) {
    override fun onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder): Boolean {
        adapter.swap(viewHolder.adapterPosition, target.adapterPosition)
        return true
    }

    override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
        adapter.remove(viewHolder.adapterPosition)
    }
})
helper.attachToRecyclerView(binding.recyclerView)

第一引数がドラッグの方向で、第二引数がスワイプ方向ですね。
ドラッグ(onMove)が発生したときはtargetと位置を入れ替える操作を、スワイプ(onSwipe)が発生したときは削除の動作をそれぞれつないでいるわけです。

こうすると、以下のようにドラッグドロップで並べ替え、スワイプで削除が実装できます

drag&drop swipe

超簡単です。

GridLayoutManager

グリッドレイアウトの場合、ドラッグ方向は上下左右になりますね。

val helper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
    ItemTouchHelper.UP or ItemTouchHelper.DOWN or
    ItemTouchHelper.START or ItemTouchHelper.END, 0
) {
    override fun onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder): Boolean {
        adapter.swap(viewHolder.adapterPosition, target.adapterPosition)
        return true
    }

    override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
        adapter.remove(viewHolder.adapterPosition)
    }
})
helper.attachToRecyclerView(binding.recyclerView)

また、並べ替えのロジックによって振る舞いが結構違います。
前項目と同様に、from/toの間を詰めて移動させる場合

1
fun swap(from: Int, to: Int) {
    list.add(to, list.removeAt(from))
    submitList(list.toList())
}

from/toを単純に入れ替える場合

2
fun swap(from: Int, to: Int) {
    Collections.swap(list, from, to)
    submitList(list.toList())
}

それぞれ、以下のような動作になります。どちらがいいか、また別のロジックが良いかはアプリの特性によって決めれば良いと思います。

1 2

以上です。

7
9
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
7
9