[Android, Kotlin] RecyclerView에서 Swipe Menu 구현하기, Delete 메뉴 + Card View [2]

https://jinudmjournal.tistory.com/112

 

[Android, Kotlin] RecyclerView에서 Swipe Menu 구현하기, Delete 메뉴 + Card View [1]

Swipe Menu with RecyclerView 리싸이클러 뷰 내부에서 item을 슬라이드할 때 DELETE 버튼이 나오도록 코드를 작성한다. 메시지 목록에서 슬라이드해서 메시지를 삭제하는 등에 사용되는 기능이다. 일반적

jinudmjournal.tistory.com

 

위 포스팅에 이은 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
}

 

이 과정을 거쳤다면 스와이프 시 아이템 뷰가 사라지지 않고 드래그가 종료되면, 제자리로 돌아가는 상태를 얻을 수 있다.

 

마지막으로 뷰를 스와이프하면 해당 리싸이클러 뷰의 아이템이 스와이프 되고, 그 자리에 버튼이 생성되는 코드를 작성하면 된다.

버튼이 생성될 경우 스와이프한 뷰는 버튼의 크기 만큼만 제자리로 돌아가도록 설정하면 버튼을 가지는 아이템 뷰를 얻을 수 있다.

 

다음 포스팅을 마지막으로 기능을 구현해야겠다.