본문 바로가기

안드로이드

RecyclerView에서 Drag&Drop을 이용해 View 위치 변경하기

반응형

1. View의 위치를 변경하기 위한 Library
안드로이드는 RecyclerView에서 각 View의 이동을 도와주기 위해 ItemTouchHelper Library를 지원하고 있다.
이 Library에 존재하는 ItemTouchHelper.Callback을 사용자가 구현하여 사용할 수 있다.
각각의 ViewHolder에 대해서 사용자가 특정 터치 동작을 일으킬 경우, ItemTouchHelper.Callback은 해당 상황에 맞는(설정된) 함수를 호출한다.

상세 문서는 아래 링크(안드로이드 개발문서)를 통해 확인할 수 있다.

https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.Callback

 

ItemTouchHelper.Callback  |  Android 개발자  |  Android Developers

ItemTouchHelper.Callback This package is part of the Android support library which is no longer maintained. The support library has been superseded by AndroidX which is part of Jetpack. We recommend using the AndroidX libraries in all new projects. You sho

developer.android.com

 

2. RecyclerView에서 Drag&Drop을 이용해 View 위치 변경 및 삭제 구현
본 예제에서는 간단한 TodoList 만들어 Drag&Drop을 구현하였다.

1. build.gradle에 recyclerview를 implement

dependencies {
    implementation 'com.android.support:recyclerview-v7:26.1.0'
}


2. ItemTouchHelperListener Interface 구현
 - Item이 이동할 때(Moved)와 지워질 때(Swiped)를 구현

interface ItemTouchHelperListener {
    fun onItemMoved(from : Int, to : Int)
    fun onItemSwiped(position : Int)
}


3. ItemTouchHelperCallback Class 구현
 - ItemTouchHelper.Callback의 getMovementFlags, onMove, onSwiped 함수 재 정의
 - getMovementFlags를 통해 동작방식 구현
 - onMove를 통해 Item이 위 아래로 움직일 때의 동작 구현
 - onSwiped를 통해 Item이 옆으로 움직일 때의 동작 구현

class ItemTouchHelperCallback(val listener : ItemTouchHelperListener) : ItemTouchHelper.Callback() {
    override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
        val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
        val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
        return makeMovementFlags(dragFlags, swipeFlags)
    }

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

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

    override fun isLongPressDragEnabled(): Boolean {
        return true
    }
}


4. ItemStartDragListener Interface구현
 - Item에 Drag가 이루어질 경우를 구현하기 위한 StartDragListener Interface 구현

interface ItemStartDragListener {
    fun onStartDrag(viewHolder : RecyclerView.ViewHolder)
}


5. RecyclerViewAdapter Class 구현
 - ItemStartDragListener를 상속하여, onItemMoved와 onItemSwiped를 구현
 - ViewHolder를 커스텀하여, 각각의 View에 onLongClicklistener 동작 세팅

class RecyclerViewAdapter(private val items : MutableList<Job>, private val listener : ItemStartDragListener) : RecyclerView.Adapter<RecyclerViewAdapter.CustomViewHolder>(), ItemTouchHelperListener {

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

    override fun getItemCount(): Int {
        return items.size
    }

    override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
        holder.bind(items[position])
    }

    override fun onItemMoved(from: Int, to: Int) {
        println("called onItemMoved")
        val fromItem = items.removeAt(from)
        items.add(to, fromItem)
        notifyItemMoved(from, to)
    }

    override fun onItemSwiped(position: Int) {
        items.removeAt(position)
        notifyItemRemoved(position)
    }

    class CustomViewHolder(itemView : View, listener : ItemStartDragListener) : RecyclerView.ViewHolder(itemView){
        init{
            itemView.item_recyclerview_layout.setOnLongClickListener {
                listener.onStartDrag(this)
                return@setOnLongClickListener true
            }
        }
        fun bind(job : Job){
            itemView.item_importance.text = job.importance
            itemView.item_content.text = job.jobContents
            itemView.item_isDone.isChecked = job.isDone
        }
    }
}


 - item_content.xml 구현(recyclerview에 들어갈 각각의 view)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_recyclerview_layout"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/item_importance"
        android:layout_width="50dp"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="25dp"
        android:textStyle="bold"/>
    <TextView
        android:id="@+id/item_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="left|center"
        android:layout_toLeftOf="@id/item_isDone"
        android:layout_toRightOf="@id/item_importance"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="2dp"
        android:layout_marginBottom="2dp"
        android:layout_marginRight="5dp"
        android:textSize="20dp"/>
    <CheckBox
        android:id="@+id/item_isDone"
        android:layout_width="50dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"/>

</RelativeLayout>


 - TodoList에 들어갈 항목들에 대해 data class 구현(중요도/내용/종료여부)

data class Job(
    val importance : String,
    val jobContents : String,
    val isDone : Boolean
)


6. MainActivity 동작 구현
 - ItemTouchHelper 객체를 생성하여 recyclerview를 연결시킨다.
 - ItemStartDragListener를 상속하여 onStartDrag 구현. item이 LongClick 될 시의 동작 구현

class MainActivity : AppCompatActivity(), ItemStartDragListener {

    private val items : MutableList<Job> = mutableListOf(
        Job("H","집안청소",false),
        Job("L","빨래하기",false),
        Job("M","저금통사기",true),
        Job("H","약먹기",true),
        Job("L","포맷하기",false)
    )

    private lateinit var  recyclerViewAdapter: RecyclerViewAdapter
    private lateinit var itemTouchHelper : ItemTouchHelper

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        recyclerViewAdapter = RecyclerViewAdapter(items,this)

        recycler_view.apply{
            adapter = recyclerViewAdapter
            layoutManager = LinearLayoutManager(context)
            addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
        }

        itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback(recyclerViewAdapter))
        itemTouchHelper.attachToRecyclerView(recycler_view)
    }

    override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
        itemTouchHelper.startDrag(viewHolder)
    }
}

 - activity_main.xml 구현(recyclerview 넣기)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:gravity="center"
    android:orientation="vertical">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"/>

</LinearLayout>

3. 동작 순서는 다음과 같다
 1) item을 long click하면, recyclerviewAdapter에 선언된 setOnLongClickListener에 의해 onLongClickListener가 동작.
 2) onLongClickListener에 의해 MainActivity에서 생성한 itemTouchHelper의 startDrag 함수를 롱클릭한 item에 실행하여  item에 drag를 시작한다.
 3) 상하좌우로 drag 하게 될 경우, itemTouchHelper의 Callback인 ItemTouchHelperCallback(Custom)에 의해 아래와 같이 두 가지의 경우로 나누어 동작하며, 4번부터 onMoved 호출 시 동작 작성
  - 위/아래 동작 : ItemTouchHelperCallback의 onMoved 함수 호출
  - 좌/우 동작 : ItemTouchHelperCallback의 onSwiped 함수 호출
 4) onMove에서 onItemMoved 함수 호출
 5) recyclerViewAdpater에서 override 된 onItemMoved 호출하여, from과 target의 position 전달
  - onItemMoved는 item이 한 칸 움직일 때마다 호출됨을 알 수 있음
 6) recyclerViewAdapter에서는 from과 to position을 바꿔주는 동작을 구현
  - from에 위치한 item을 제거하고 그 item을 to의 위치에 add
  - 이후 notifyItemMoved를 통해 Adapter에 알려준다
 7) onSwiped도 이와 같이 동작

반응형

'안드로이드' 카테고리의 다른 글

Android 개발이야기(1)  (0) 2019.12.06