Android
Kotlin
swipe
RecyclerView
drag&drop

RecyclerViewでドラッグアンドドロップの移動とスワイプの削除

検証環境

この記事の内容は、以下の環境で検証した。
* Java:open jdk 1.8.0_144
* Kotlin 1.2.50
* Android Studio 3.1.4
* CompileSdkVersion:27

はじめに

これまでRecyclerViewの記事をいくつか書いてきました。
ここまで来るとやっぱりドラッグアンドドロップやスワイプによる操作をしたくなります。
今回はそんな実装方法を説明していきます。

完成イメージ

この記事でのゴールは下記のアプリです。

基本的なRecyclerViewについて

下記の記事を参照してください。

RecyclerViewの基本

実装手順

基本的なRecyclerViewの実装方法は過去の記事を参照してください。

  1. android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback#SimpleCallbackインターフェイスの実装
  2. android.support.v7.widget.helper.ItemTouchHelper#ItemTouchHelperオブジェクトの生成
  3. RecyclerViewをItemTouchHelperオブジェクトをアタッチ

実装内容の説明

概要

ドラッグアンドドロップやスワイプの実装を行うにはItemTouchHelperクラスのオブジェクトが必要になります。
ItemTouchHelperクラスのコンストラクタにはItemTouchHelper.Callbackクラスのオブジェクトを渡す必要があります。しかし、このItemTouchHelper.Callbackクラスは抽象クラスで実装するにはハードルがとても高いクラスです。そこで、Androidでは、ItemTouchHelper.Callbackクラスを継承したItemTouchHelper.SimpleCallbackクラスを提供しています。ItemTouchHelper.SimpleCallbackクラスはシンプルな抽象クラスとなっており、ドラッグアンドドロップとスワイプしたときの処理のみを記述するだけで実装が可能です。本記事では、ItemTouchHelper.SimpleCallbackクラスを使用します。

android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback#SimpleCallbackクラスの実装

まずは実装したコード全体を確認していきます。今回はActivity内に実装しています。

package jp.co.casareal.recyclerview_swipe_delete

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
import android.util.Log
import jp.co.casareal.recyclerview_swipe_delete.adapter.RowAdapter
import jp.co.casareal.recyclerview_swipe_delete.utils.Utils
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val datasets = Utils.createDatasets()

        myRecyclerView.adapter = RowAdapter(datasets)
        myRecyclerView.layoutManager = LinearLayoutManager(this)


        val itemTouchHelper = ItemTouchHelper(object : SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {

            override fun onMove(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, target: RecyclerView.ViewHolder?): Boolean {

                val fromPosition = viewHolder?.adapterPosition ?: 0
                val toPosition = target?.adapterPosition ?: 0

                myRecyclerView.adapter.notifyItemMoved(fromPosition, toPosition)

                return true
            }

            override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) {
                viewHolder?.let {
                    myRecyclerView.adapter.notifyItemRemoved(viewHolder.adapterPosition)
                }
            }
        })
        itemTouchHelper.attachToRecyclerView(myRecyclerView)
    }
}

ドラッグアンドドロップとスワイプの方向の指定

一つずつ確認をしていきます。
まずは、ドラッグアンドドロップとスワイプが動作する時の方向の指定です。

object : SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT

ここでは、ItemTouchHelper.SimpleCallbackクラスの無名オブジェクトを作成しています。
コンストラクタの定義は以下のようになっています。

ItemTouchHelper.SimpleCallback(int dragDirs, int swipeDirs)

引数の意味は以下のようになっています。

第1引数
ドラッグアンドドロップで有効にする動作の方向をORで指定する
第2引数
スワイプで有効にする動作の方向を指定する

引数に渡せる値は以下となっています。

意味
ItemTouchHelper.UP 上方向への操作
ItemTouchHelper.DOWN 下方向への操作
ItemTouchHelper.RIGHT 右方向への操作
ItemTouchHelper.LEFT 左方向への操作
ItemTouchHelper.START 左方向への操作
ItemTouchHelper.END 右方向への操作

今回の例では、上下へのドラッグアンドドロップと右から左へのスワイプで操作するように指定しています。

ドラッグアンドドロップした際に呼び出されるメソッドの実装

ドラッグアンドドロップがコンストラクタで指定した方法でそうされると下記のメソッドが呼び出されます。

override fun onMove(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, target: RecyclerView.ViewHolder?): Boolean {
}

メソッドの引数は下記のとおりです。

引数 説明
第1引数 ドラッグアンドドロップが発生したRecyclerViewのオブジェクト。
第2引数 ドラッグアンドドロップした開始時のオブジェクト。開始時のポジション情報を保持している。
第3引数 ドラッグアンドドロップ完了時のオブジェクト。ドラッグアンドドロップ完了時のポジション情報を保持している。

特に第2引数と第3引数のポジション情報を利用して、表示しているデータの順序を交換などを行います。

val fromPosition = viewHolder?.adapterPosition ?: 0
val toPosition = target?.adapterPosition ?: 0

上記のコードでは、ドラッグアンドドロップした開始時のポジションと完了のポジション情報を取得しています。

myRecyclerView.adapter.notifyItemMoved(fromPosition, toPosition)

上記のコードはAdapterにドラッグアンドドロップされたことを通知します。 もし記述しないと、ドラッグアンドドロップが有効になりません。

return true

上記はドラッグアンドドロップが正常に終了した場合にtrueを返します。
trueを返すとonMovedメソッドが呼び出されます。

注意事項
この状態ではAdapterが表示しているデータそのものは入れ替わっていません。
そのため、notifyDataSetChangedメソッドが呼び出されるともとの状態に戻ってしまいます。

onMovedメソッドでデータの入れ替えが必要になります。

スワイプした際に呼び出されるメソッドの実装

続いてスワイプしたときに呼び出されるメソッドについて説明します。
スワイプが指定した方向で操作されると、下記のメソッドが呼び出されます。

override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) {
}

メソッドの引数は以下の通りです。

引数 説明
第1引数 スワイプされたViewHolderのオブジェクト
第2引数 スワイプしたポジション

上記の引数を活用して、Adapterが保持しているデータを削除する必要があります。
ドラッグアンドドロップ同様に、削除されたことをAdapterに通知する必要があります。
下記のメソッドを呼び出すことにより、実際に一覧から削除されます。

myRecyclerView.adapter.notifyItemRemoved(viewHolder.adapterPosition)

注意事項
この状態ではAdapterが表示しているデータそのものは削除されません。
そのため、notifyDataSetChangedメソッドが呼び出されるともとの状態に戻ってしまいます。

まとめ

ドラッグアンドドロップとスワイプによる操作の実装は、思ったより簡単にできます。いろいろな箇所に利用できそうなので覚えておいても損はなさそうです。