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

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

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

# 完成イメージ
この記事でのゴールは下記のアプリです。

<img src="https://qiita-image-store.s3.amazonaws.com/0/99960/c651c3e6-b440-61e0-a230-79ced472bdd5.gif" width=50% >

# 基本的なRecyclerViewについて
下記の記事を参照してください。

[RecyclerViewの基本](https://qiita.com/naoi/items/f8a19d6278147e98bbc2)


# 実装手順
基本的な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内に実装しています。

```kotlin
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)
    }
}
```
## ドラッグアンドドロップとスワイプの方向の指定

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

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

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

> ItemTouchHelper.SimpleCallback(int dragDirs, int swipeDirs)

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

|第１引数|
|:---|
|ドラッグアンドドロップで有効にする動作の方向をORで指定する|

|第2引数|
|:---|
|スワイプで有効にする動作の方向を指定する|

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

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

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

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

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

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

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

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

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

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

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

```kotlin
myRecyclerView.adapter.notifyItemMoved(fromPosition, toPosition)
```

上記のコードはAdapterにドラッグアンドドロップされたことを通知します。 **もし記述しないと、<font color="Red">ドラッグアンドドロップが有効になりません。</font>** 

```kotlin
return true
```

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

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

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

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

```kotlin
override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) {
}
```
メソッドの引数は以下の通りです。

|引数|説明|
|:---|:--|
|第１引数|スワイプされたViewHolderのオブジェクト|
|第２引数|スワイプしたポジション|

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

```kotlin
myRecyclerView.adapter.notifyItemRemoved(viewHolder.adapterPosition)
```
 **<font color="Red">注意事項</font>**
この状態ではAdapterが表示しているデータそのものは削除されません。
そのため、notifyDataSetChangedメソッドが呼び出されるともとの状態に戻ってしまいます。


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