https://jinudmjournal.tistory.com/112
위 포스팅에 이은 2번째 포스팅입니다.
Swipe Menu with RecyclerView - ItemTouchHelper 사용
ItemTouchHelper는 RecyclerView에서 스와이프 및 드래그 앤 드롭을 지원하는 유틸리티 클래스이다.
ItemTouchHelper는 사용자가 액션을 수행할 때 이벤트를 수신하는
RecyclerView와 이벤트에 반응하는 Callback 클래스와 함께 사용된다.
ItemTouchHelper의 Callback 클래스를 사용하여 ViewHolder에 대한 터치 기능을 정의하고,
사용자가 정의한 액션을 수행할 때 콜백 함수를 받는다.
주로 4가지 메서드를 사용한다.
ItemTouchHelper - Callback의 클래스 주요 메서드
1. getMovementFlags (RecyclerView, ViewHolder)
각각의 뷰에 수행할 수 있는 작업을 컨트롤 하기 위해서는 해당 메소드를 오버라이드 후 방향을 반환해야 한다.
2. makeMovementFlags(int, int)
SimpleCallback or makeMovementFlags를 사용해서 방향을 지정할 수 있다.
3. onMove (RecyclerView, dragged, target)
사용자가 아이템을 드래그 할 때 ItemTouchHelper에서 onMove 메서드를 호출한다.
이 콜백 메서드를 통해서 어댑터에서 아이템을 이전 위치에서 새로운 위치로 이동한다.
이 후에 RecyclerView의 Adapter에서 notifyItemMoved()를 호출하여 데이터를 갱신해야 한다.
dragged.adapterPosition -> target.adapterPosition
4. onSwiped (ViewHolder, Int)
View가 스와이프되면 ItemTouchHelper는 범위가 벗어날 때까지 View를 애니메이션화한 다음 이 메서드를 호출한다.
Class : SwipeContoller
위의 메서드를 사용하여 swipe를 동작하기 위해서 SwipeController 클래스를 선언한다.
이 클래스에서 기능을 작성하고, attachToRecyclerView를 통해서 리싸이클러 뷰에 적용시키면 된다.
class SwipeController : ItemTouchHelper.Callback()
callback 클래스의 메서드를 사용하기 위해서 상속받는다.
이 후에 주요 메서드를 선언했다.
makeMovementFlags에는 좌우 스와이프만 사용하므로 swipeFlags의 값을 LEFT,RIGHT를 선언했다.
@SuppressLint("RtlHardcoded")
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
return makeMovementFlags(0,swipeFlags)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
Class : SwipeControllerActions
스와이프하여 나온 버튼을 클릭하였을 때 동작을 지시하기 위한 클래스이다.
인터페이스로 선언하였으며, 해당 기능에 대한 구현은 기능을 구현 할 Activity에서 한다.
interface SwipeControllerActions {
fun onLeftClicked(position :Int){
}
fun onRightClicked(position: Int){
}
}
모든 준비가 끝났으므로 RecyclerView에 해당 클래스를 연결해야 한다.
attachToRecyclerView를 사용한다.
val swipeController = SwipeController()
val itemTouchHelper = ItemTouchHelper(swipeController)
itemTouchHelper.attachToRecyclerView(binding.recyclerView)
이 단계가지 거치면 프로젝트에서 리싸이클러 뷰에 대한 스와이프가 활성화된다.
좌우로 리싸이클러 뷰의 항목을 스와이프할 경우 해당 아이템이 사라지게 된다.
뷰에 대한 스와이프 제한
더 이상 뷰가 스와이프 되지 않도록 차단해야 하며, 이 후에 버튼을 나타내는 코드를 작성한다.
convertToAbsoluteDirection를 재정의하여 ItemTouchHelper.Callback에서 뷰의 스와이프 정도를 설정할 수 있다.
override fun convertToAbsoluteDirection(flags: Int, layoutDirection: Int): Int {
if (swipeBack){
swipeBack = buttonShowedState != ButtonsState.GONE
return 0
}
return super.convertToAbsoluteDirection(flags, layoutDirection)
}
private var swipeBack : Boolean = false // 스크롤 시 끝 지정
convertToAbsoluteDirection를 오버라이드하여 기능을 정의한다.
RecyclerView의 onTouchListener를 설정하여 스와이프가 마친 후 swipeBack을 true로 설정한다.
Child View가 그려질 때 적용되는 함수인 onChildDraw를 오버라이드하여 아래와 같이 기능을 작성한다.
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
// 스와이프 상태일 때 적용
if (actionState==ACTION_STATE_SWIPE){
if (buttonShowedState != ButtonsState.GONE){
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}else{
setTouchListener(c,recyclerView,viewHolder,dX,dY
,actionState, isCurrentlyActive)
}
}
if (buttonShowedState == ButtonsState.GONE){
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}
ButtonShowedState는 현재 버튼의 상태를 나타내며, 아래와 같이 정의된다.
// 버튼의 상태를 나타냄
private var buttonShowedState : ButtonsState = ButtonsState.GONE
ButtonState는 enum class로 정의하였으며, 버튼의 3가지 상태를 저장하는 용도 사용된다.
enum class ButtonsState {
GONE,
LEFT_VISIBLE,
RIGHT_VISIBLE
}
이 과정을 거쳤다면 스와이프 시 아이템 뷰가 사라지지 않고 드래그가 종료되면, 제자리로 돌아가는 상태를 얻을 수 있다.
마지막으로 뷰를 스와이프하면 해당 리싸이클러 뷰의 아이템이 스와이프 되고, 그 자리에 버튼이 생성되는 코드를 작성하면 된다.
버튼이 생성될 경우 스와이프한 뷰는 버튼의 크기 만큼만 제자리로 돌아가도록 설정하면 버튼을 가지는 아이템 뷰를 얻을 수 있다.
다음 포스팅을 마지막으로 기능을 구현해야겠다.