【Kotlin/Android Studio】ItemTouchHelperの使い方!RecyclerViewでスワイプ処理を実装する方法

この記事からわかること
- Android Studio/KotlinでRecyclerViewにスワイプ処理を実装する方法
- ItemTouchHelper.SimpleCallbackの使い方
- スワイプの背景色やアイコンの設定
index
[open]
\ アプリをリリースしました /
参考文献:公式リファレンス:ItemTouchHelper.SimpleCallback
環境
- Android Studio:Flamingo
- Kotlin:1.8.20
RecyclerViewでスワイプ処理
AndroidのRecyclerViewではリストアイテムをスワイプした際にアイテムを削除したりといったアクションを追加することが可能です。

それを実現させるのがItemTouchHelper.SimpleCallback
クラスです。このクラスはリストアイテムのドラッグやスワイプといった仕組みを提供しています。各アクションが実行された際には適当なコールバックメソッドが呼び出されるようになっており、任意の処理を実行させることが可能になっています。
abstract class ItemTouchHelper.SimpleCallback : ItemTouchHelper.Callback
実装の流れ
実装方法は少しややこしいので流れと方法を1つずつ見ながら実装していきたいと思います。とりあえずスワイプ時に削除する処理が走るようにしていきます。
- ItemTouchHelperを継承したクラスの作成
- スワイプの方向を指定
- コールバックメソッド内を実装
- Adapter側にDeleteメソッドを用意
ItemTouchHelperを継承したクラスの作成
RecyclerViewにスワイプアクションを追加するにはItemTouchHelper.SimpleCallback
クラスを継承させます。クラス名は何でも良いですがここではSwipeToCallback
クラスを用意して引数として対象のRecyclerViewのAdapterを受け取れるようにしておきます。
class SwipeToCallback(private val adapter: MyListAdapter) : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.ACTION_STATE_IDLE, // ドラッグの方向を指定
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT // スワイプの方向を指定
) {
// 〜〜〜〜〜〜〜〜〜〜〜〜〜〜
}
スワイプの方向を指定
スワイプの方向はItemTouchHelper.SimpleCallback
クラスの引数から指定します。1つ目の引数にはドラッグ方向を、2つ目の引数にはスワイプ方向を指定します。
ItemTouchHelper.SimpleCallback(
ItemTouchHelper.ACTION_STATE_IDLE, // サポートしない
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT // 左と右のスワイプをサポート
)
指定するのは以下の項目です。ItemTouchHelper.UP
やItemTouchHelper.DOWN
に関しては単体では動作確認ができず、ItemTouchHelper.LEFT
などと一緒に指定することで上や下に動かせるようになりました。また複数の値を指定したいときはor
を使用します。
指定値(フラグ) | 概要 |
---|---|
ItemTouchHelper.UP | 上方向 |
ItemTouchHelper.DOWN | 下方向 |
ItemTouchHelper.START | Viewの開始方向 |
ItemTouchHelper.END | Viewの終了方向 |
ItemTouchHelper.LEFT | 左方向 |
ItemTouchHelper.RIGHT | 右方向 |
ItemTouchHelper.ACTION_STATE_IDLE | サポートしない(厳密にはアイドル状態を示すフラグ) |
その他の状態を示すフラグや詳細な内容は公式サイトを参考にしてください。
コールバックメソッド内を実装
スワイプ方向の指定ができたらスワイプ時に実行させたい処理を記述します。onMove
メソッドはドラッグ時の処理なのでここでは何も記述しません。onSwiped
メソッド内に実行したい処理を記述します。
class SwipeToCallback(private val adapter: MyListAdapter) : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.ACTION_STATE_IDLE, // ドラッグの方向を指定
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT // スワイプの方向を指定
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
// ドラッグアンドドロップの処理を実装
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// スワイプしたアイテムを削除する処理を実装
val position = viewHolder.adapterPosition
adapter.deleteItem(position)
}
}
onSwiped
の引数viewHolder
のadapterPosition
からスワイプされたリストアイテムインデックスを参照できます。少し順番が逆になりますが次でアダプター側(今回はMyListAdapter)のdeleteItem
メソッドを用意します。
Adapter側にDeleteメソッドを用意
Adapterクラスに先ほど呼び出していた削除するためのメソッドを用意します。ここでは引数として受け取ったインデックス番号を元にデータソースの削除とRecyclerViewの見た目上の削除(notifyItemRemoved
)を行なっています。
fun deleteItem(position: Int) {
if (position < 0 || position >= _userList.size) {
return
}
_userList.removeAt(position)
notifyItemRemoved(position)
}
RecyclerViewとの紐付け
RecyclerView
と紐づけるためには以下のように記述します。
val recyclerView: RecyclerView = view.findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(view.context)
recyclerView.addItemDecoration(
DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL)
)
val adapter = MyListAdapter(data)
val swipeToCallback = SwipeToCallback(adapter)
val itemTouchHelper = ItemTouchHelper(swipeToCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
recyclerView.adapter = adapter
これでスワイプ時にデータを削除するような処理の実装が完了しました。この状態ではスワイプ時の背景色などを設定していないためスワイプともにそのままデータが消えていく感じなります。
スワイプ時の背景色やアイコンを設置する
スワイプ時の背景色を変更したり、アイコンを設置するにはonChildDraw
メソッドをオーバーライドして記述します。
// スワイプ時の背景色とアイコンを描画
override fun onChildDraw(
canvas: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
val background: ColorDrawable = ColorDrawable(Color.RED)
// スワイプ時のアイコンを定義AppCompatResources
val deleteIcon: Drawable? = AppCompatResources.getDrawable(recyclerView.context,R.drawable.delete)
if (deleteIcon != null) {
val itemView = viewHolder.itemView
val iconMargin = (itemView.height - deleteIcon.intrinsicHeight) / 2
val iconTop = itemView.top + (itemView.height - deleteIcon.intrinsicHeight) / 2
val iconBottom = iconTop + deleteIcon.intrinsicHeight
when {
dX > 0 -> { // 右方向へのスワイプ
val iconLeft = itemView.left + iconMargin
val iconRight = itemView.left + iconMargin + deleteIcon.intrinsicWidth
deleteIcon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
background.setBounds(itemView.left, itemView.top, itemView.left + dX.toInt(), itemView.bottom)
}
dX < 0 -> { // 左方向へのスワイプ
val iconLeft = itemView.right - iconMargin - deleteIcon.intrinsicWidth
val iconRight = itemView.right - iconMargin
deleteIcon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
background.setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom)
}
else -> {
background.setBounds(0, 0, 0, 0)
}
}
background.draw(canvas)
deleteIcon.draw(canvas)
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。
個人開発に限界を感じたらiOSに特化したプログラミングスクール「iOSアカデミア」も検討してみてください!無料相談可能で「最短・最速」でiOSエンジニアになれるように手助けしてくれます。