[Android] ItemTouchHelper - Drag / Swipe

Jiyoung Hwang·2022년 8월 14일
0

Android

목록 보기
1/2
post-thumbnail

ItemTouchHelper

open class ItemTouchHelper : RecyclerView.ItemDecoration, RecyclerView.OnChildAttachStateChangeListener
  • RecyclerView의 ItemDecoration과 OnChildAttachStateChangeListener의 상속을 받는 open class
  • RecyclerView에서 dragswipe 를 사용할 수 있는 유틸리티 클래스
  • 기본적으로는 가로(x)/세로(y)로 이동하는 것이 default이지만, ItemTouchHelper.Callback.onChildDraw() or ItemTouchHelper.Callback.onChildDrawOver()를 사용하면 새로운 움직임을 정의하여 사용할 수 있다.

RecyclerView와 ItemTouchHelper 연결

  • attachToRecyclerView() 를 통해 RecyclerView 에 ItemTouchHelper를 attach 한다.
private lateinit var recyclerView: RecyclerView
private lateinit var recyclerViewItemTouchHelper: RecyclerViewItemTouchHelper
private lateinit var itemTouchHelper: ItemTouchHelper


// ItemTouchHelper를 상속받아 정의한 class 객체 생성
recyclerViewItemTouchHelper = RecyclerViewItemTouchHelper(adapter)
// ItemTouchHelper에 직접 정의한 ItemTouchHelper 등록
itemTouchHelper = ItemTouchHelper(recyclerViewItemTouchHelper)
// RecyclerView에 직접 정의한 ItemTouchHelper attach!
itemTouchHelper.attachToRecyclerView(recyclerView)  
  • ItemTouchHelper는 RecyclerView에 attach되는 것이므로, RecyclerView가 detached된다면, ItemTouchHelper는 clearView()를 호출한다.

ItemTouchHelper의 상태

ACTION_STATE_IDLE : 정지 상태 (아무런 action이 취해지지 않았을 때)
ACTION_STATE_SWIPE : 뷰가 현재 스와이프되어지고 있는 상태
ACTION_STATE_DRAG : 뷰가 현재 드래그되어지고 있는 상태

ItemTouchHelper의 방향

ItemTouchHelper.UP
ItemTouchHelper.DOWN
ItemTouchHelper.LEFT
ItemTouchHelper.RIGHT


Callback vs SimpleCallback

하는 역할은 같은데 방향을 정의하는 방식에 차이가 있는 것 같다.
Callback 은 개발자가 직접 getMovementFlags() 를 통해 방향을 설정하는데 SimpleCallback생성자를 통해 방향을 설정한다.

ItemTouchHelper.Callback

  • ItemTouchHelper와 application 사이를 연결해주는 class
  • 각각의 ViewHolder 마다의 touch 행동을 조절할 수 있고, 사용자로부터 받는 action 에 대해 callback을 받아 알맞는 이벤트를 발생시킨다.

ItemTouchHelper.SimpleCallback

class RecyclerViewItemTouchHelper() : ItemTouchHelper.SimpleCallback(
    ItemTouchHelper.UP or ItemTouchHelper.DOWN, 	// drag 방향
    ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT	// swipe 방향
)

필수 override method

getMovementFlags()

public abstract int getMovementFlags (
	RecyclerView recyclerView, 
    RecyclerView.ViewHolder viewHolder
)
  • idle, swiping, dragging과 같은 각각의 상태에서 새로운 상태로의 이동 flag를 생성하여 return
    예를들면 정지 상태에서 스와이프 한다거나(왼쪽/오른쪽), 드래그 상태에서 정지 상태로 변하거나(드래그 그만하기) 할때의 이동 방향을 설정하는 것
  • makeMovementFlags() or makeFlag()를 통해 편하게 flag를 생성할 수 있다.
    왜 편하게 라는 단어를 사용하냐면, flag는 8bit로 이루어진 3개의 세트로 구성되어 있다.
    각각의 방향마다 OR 연산을 해주어도 되지만, 만들어져있는 메소드를 사용하는게 조금 더 편하지 않는가..~?
  • SimpleCallback에서는 override하지 않아도 된다. 생성자에서 정의하기 때문에 할 필요가 없음

makeFlag() : 현재의 actionState 에서 어느 direction으로 움직일 것인지 정의하는 메소드

public static int makeFlag (int actionState, int directions)
  • 정지된 상태에서 위/아래로 움직이고 싶을 때
makeFlag(ItemTouchHelper.ACTION_STATE_IDLE, UP | DOWN)
  • 드래그 상태에서 오른쪽/왼쪽으로 움직이고 싶을 때
makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, LEFT | RIGHT)

makeMovementFlags() : 이동 flag를 만들 수 있는 편리한 메소드

public static int makeMovementFlags (int dragFlags, int swipeFlags)

makeFlag() 는 현재 상태일 때 이 방향으로 움직이기! 와 같이 현재의 상태 도 정의해주어야 했다면, makeMovementFlags() 는 이동할 방향만 설정해주면 된다.

override fun getMovementFlags(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ): Int {
        val drag = ItemTouchHelper.UP or ItemTouchHelper.DOWN
        val swipe = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
        return makeMovementFlags(drag, swipe)
    }

개발자 입장에서는 makeFlag() 보다 간편하게 방향을 설정할 수 있지만, 사실 makeMovementFlags()의 내부에서는 makeFlag()를 통해 움직임의 방향을 설정한다.

public static int makeMovementFlags(int dragFlags, int swipeFlags) {
            return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags)
                    | makeFlag(ACTION_STATE_SWIPE, swipeFlags)
                    | makeFlag(ACTION_STATE_DRAG, dragFlags);
        }

onMove()

public abstract boolean onMove (
	RecyclerView recyclerView, 
    RecyclerView.ViewHolder viewHolder, 
    RecyclerView.ViewHolder target
)
  • item 이 새로운 위치로 드래그 되었을 때 호출되는 메소드
  • return : 이동될 Item이 새로운 위치까지 이동이 되었는지에 대한 여부
    true -> 새로운 위치로 이동 성공
    false -> 새로운 위치로 이동 실패
  • old position : viewHolder의 adapterPosition을 통해 위치를 얻을 수 있다.
  • new position : target의 adapterPosition을 통해 위치를 얻을 수 있다.

onSwiped()

public abstract void onSwiped (
	RecyclerView.ViewHolder viewHolder, 
    int direction
)
  • 사용자로부터 item(viewHolder)이 스와이프 되었을 때 호출되는 메소드
  • 만약 getMovementFlags()를 통해 스와이프의 이동 방향을 정의할 때 START, END로 방향을 정하였다면, onSwiped()의 direction 또한 START, END의 방향(값)을 가지게 된다.

ItemTouchHelper 예제

Drag / Swipe 화면

DragSwipe

예제 소스코드

RecyclerViewItemTouchHelper : ItemTouchHelper.Callback 구현 class

import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView

class RecyclerViewItemTouchHelper(
    private val itemTouchHelperListener: ItemTouchHelperListener
) : ItemTouchHelper.Callback() {
    override fun getMovementFlags(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ): Int {
        val drag = ItemTouchHelper.UP or ItemTouchHelper.DOWN
        val swipe = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
        return makeMovementFlags(drag, swipe)
    }

    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        itemTouchHelperListener.onItemDragged(viewHolder.adapterPosition, target.adapterPosition)
        return true
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        itemTouchHelperListener.onItemSwiped(viewHolder.adapterPosition)
    }
}

RecyclerViewItemTouchHelper2 : ItemTouchHelper.SimpleCallback 구현 class

import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView

class RecyclerViewItemTouchHelper2(
    private val itemTouchHelperListener: ItemTouchHelperListener
) : ItemTouchHelper.SimpleCallback(
    ItemTouchHelper.UP or ItemTouchHelper.DOWN,
    ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) {
    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        itemTouchHelperListener.onItemDragged(viewHolder.adapterPosition, target.adapterPosition)
        return true
    }

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

ItemTouchHelperListener : drag, swipe 이벤트 처리를 위한 인터페이스

interface ItemTouchHelperListener {

    fun onItemDragged(from: Int, to: Int)

    fun onItemSwiped(position: Int)

}

RecyclerViewActivity : RecyclerView 정의 + ItemTouchHelper attach 한 class

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.study_android.R

class RecyclerViewActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: RecyclerViewAdapter

    private lateinit var recyclerViewItemTouchHelper: RecyclerViewItemTouchHelper
    private lateinit var itemTouchHelper: ItemTouchHelper

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

        val recyclerViewList = arrayListOf<RecyclerViewData>()

        for(i in 1..50) {
            recyclerViewList.add(RecyclerViewData(i, "테스트 $i"))
        }

        recyclerView = findViewById(R.id.main_recyclerview)
        adapter = RecyclerViewAdapter()

        // ItemTouchHelper를 상속받아 정의한 class 객체 생성
        recyclerViewItemTouchHelper = RecyclerViewItemTouchHelper(adapter)
        // ItemTouchHelper에 직접 정의한 ItemTouchHelper 등록
        itemTouchHelper = ItemTouchHelper(recyclerViewItemTouchHelper)
        // RecyclerView에 직접 정의한 ItemTouchHelper attach!
        itemTouchHelper.attachToRecyclerView(recyclerView)

        adapter.setList(recyclerViewList)

        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)
    }
}

RecyclerViewAdapter : RecyclerView의 Adapter 구현 class

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.study_android.R
import java.util.ArrayList

class RecyclerViewAdapter : RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewViewHolder>(), ItemTouchHelperListener {

    private var recyclerViewList = arrayListOf<RecyclerViewData>()

    class RecyclerViewViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val number: TextView
        val text: TextView

        init {
            number = view.findViewById(R.id.recyclerview_number)
            text = view.findViewById(R.id.recyclerview_text)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.activity_recyclerview_item, parent, false)
        return RecyclerViewViewHolder(view)
    }

    override fun onBindViewHolder(holder: RecyclerViewViewHolder, position: Int) {
        holder.number.text = recyclerViewList[position].number.toString()
        holder.text.text = recyclerViewList[position].text
    }

    override fun getItemCount(): Int = recyclerViewList.size

    fun setList(list: ArrayList<RecyclerViewData>) {
        this.recyclerViewList = list
    }

    override fun onItemDragged(from: Int, to: Int) {
        val oldData = recyclerViewList[from]

        recyclerViewList.removeAt(from)
        recyclerViewList.add(to, oldData)

        notifyItemMoved(from, to)
    }

    override fun onItemSwiped(position: Int) {
        recyclerViewList.removeAt(position)
        notifyDataSetChanged()
    }
}

RecyclerViewData : data class

data class RecyclerViewData(
    val number: Int,
    val text: String
)

출처

Android Developer - ItemTouchHelper
Android Developer - ItemTouchHelper.Callback
changhee09님 velog - ItemTouchHelper

profile
sin prisa pero sin pausa

0개의 댓글