[Android] RecyclerView swipe 아이템 삭제

곽호택·2021년 10월 28일
0

안드로이드

목록 보기
10/16
post-thumbnail
post-custom-banner

! 안드로이드 심화 스터디에서 각 과제에 대해 파트별로 나눠서 자료를 만들어서 공부하기로 진행했다. 내가 이번에 맡은 부분은 리사이클러뷰 아이템을 스와이프해서 지우거나, 위치를 바꾸는 것이다.

1. RecyclerView Adapter 만들기

fragment_follower.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.profile.FollowerFragment">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_follow"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginStart="10dp"
            android:layout_marginTop="20dp"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

FollwAdapter.kt

class FollowAdapter : RecyclerView.Adapter<FollowAdapter.FollowViewHolder>() {

    private var followData = mutableListOf<FollowData>()


    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): FollowAdapter.FollowViewHolder {
        val binding = ItemFollowerBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )

        return FollowViewHolder(binding)
    }

    override fun onBindViewHolder(holder: FollowAdapter.FollowViewHolder, position: Int) {
        holder.onBind(followData[position])
        holder.binding.root.setOnClickListener {
            val intent = Intent(holder.itemView?.context, DetailActivity::class.java)
            intent.putExtra("userImage", followData[position].image)
            intent.putExtra("userName", followData[position].name)
            intent.putExtra("userIntroduce", followData[position].introduce)
            startActivity(holder.itemView.context, intent, null)

        }
    }

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

    class FollowViewHolder(
        val binding: ItemFollowerBinding
    ) : RecyclerView.ViewHolder(binding.root){
        fun onBind(followData: FollowData){
            binding.apply{
                follow = followData
                binding.executePendingBindings()


            }

        }
    }
    
	// 데이터 전체 값 갱신
    fun setFollowData(followData: MutableList<FollowData>){
        this.followData = followData
        notifyDataSetChanged()
    }

    fun removeData(position: Int){
        followData.removeAt(position)
        notifyItemRemoved(position)
    }

    fun swapData(fromPos: Int, toPos: Int){
        Collections.swap(followData, fromPos, toPos)
        notifyItemMoved(fromPos, toPos)
    }
}

기존에 RecyclerView 만드는 것과 동일하게 Adapter를 만들었지만 추가 된 부분이 removeData와 swapData함수이다.

removeData는 말 그대로 데이터를 삭제하는 부분!

swapData는 아이템의 위치를 서로 바꿔주는 부분이다.

1-2 removeData()

removeAt()

removeAt()은 가변형 Collection에서 사용되는 것으로 해당 인덱스의 인자를 삭제한다.

추가 적으로

  • add(E) : 인자 추가 후 true 반환/ 요소가 있거나 중복 불허인 경우 false

  • addAll, removeAll : 컬렉션 인자를 모두 추가/ 삭제

  • retainAll: 컬렉션 인자를 받아 해당 요소만 보유

  • clear() : 컬렉션 모든 요소를 삭제

notifyItemRemoved()

나는 recyclerView의 리스트를 업데이트 할때 사용하는 함수로 notifyDataSetChanged()를 사용해왔다.

하지만 notifyDataSetChanged()는 리스트의 크기와 아이템이 둘 다 변경되는 경우에 사용해야 한다. LayoutManager가 모든 자료를 다시 바인딩 하고, 모든 View를 다시 레이아웃하게 된다. 즉 아이템 전체를 다시 업데이트 하기 때문에 굉장히 비효율적이다. 그러므로 때에 따라 다른 메소드를 사용해야 할 필요가 있다.

  • notifyItemChanged(position) : 어느 특정 위치의 아이템만 변경할 경우에 사용.

  • notifyItemRangeChanged(positionStart, itemCount)
    -- positionStart : 변경된 첫 번째 아이템의 위치
    -- itemCount: 변경된 아이템의 개수
    -- 변경된 아이템이 연속된 여러 개의 아이템일 때 이 함수를 사용한다.

  • notifyItemInserted(position)
    -- 특정 위치에 아이템이 새로 삽입했을 때 사용한다.

  • notifyItemRagneInserted(positionStart, itemCount)
    -- notifyItemRangeChanged와 비슷하게 연속된 여러 개의 아이템이 삽입될 때 사용한다.

  • notifyItemRemoved(position) : 특정한 아이템 1개를 삭제할 때 사용.

  • notifyItemRangeRemoved(position, itemCount)
    -- 연속된 여러 개의 아이템이 삭제될 때 사용

  • notifyItemMoved(fromPosition, toPosition)
    -- fromPosition : 아이템의 이전 위치
    -- toPosition : 아이템의 새로운 위치
    -- 아이템이 이동했을 때 사용하는 함수

1-3 swapData()

아이템의 위치를 바꿔주는 함수이다

java에서 제공하는 Collections.swap()를 사용해서 followData의 fromPos 위치에 있는 아이템과 toPos 위치에 있는 아이템을 바꾸는 것이다.

그 뒤 위에서 본 notifyItemMoved()를 통해서 recyclerView를 갱신해준다.

2. ItemTouchHelper 클래스 구현

FollowerFragment.kt

class FollowerFragment : BaseFragment<FragmentFollowerBinding>(R.layout.fragment_follower) {
    private lateinit var followAdapter: FollowAdapter
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initFollow()
        itemTouch()
    }


    private fun initFollow(){
        followAdapter = FollowAdapter()
        binding.recyclerFollow.adapter = followAdapter
        binding.recyclerFollow.addItemDecoration(MyDecoration(50, Color.BLUE))
        followAdapter.setFollowData(
            mutableListOf(
                FollowData(R.drawable.hotaek, "곽호택", "안드로이드 OB"),
                FollowData(R.drawable.hotaeks, "곽호택", "차로 갓갓갓갓"),
                FollowData(R.drawable.hotaekes, "곽호택", "솝트 갓갓갓갓")
            )
        )
    }

기존에 RecyclerVIew 작성할때와 동일하게 작성했다.
addItemDecoration은 구분선을 넣기 위해서 작성했다.

private fun itemTouch(){
      val itemTouchCallback = object : ItemTouchHelper.SimpleCallback(
          ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT
      ){
          override fun onMove(
              recyclerView: RecyclerView,
              viewHolder: RecyclerView.ViewHolder,
              target: RecyclerView.ViewHolder
          ): Boolean {
              val fromPos: Int = viewHolder.adapterPosition
              val toPos: Int = target.adapterPosition
              followAdapter.swapData(fromPos, toPos)
              return true
          }

          override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
              followAdapter.removeData(viewHolder.layoutPosition)
          }
      }

      ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerFollow)
  }

아이템 삭제와 스왑을 위한 함수이다.

Object Expression(객체 표현식)을 사용하였는데 객체 표현식은 특정 타입으로 상속되는 익명 클래스의 객체를 생성하기 위해 사용한다.

이와 같은 방식은 일회용으로 사용하기에 유용하다.

그래서 위의 코드에서 ItemTouchHelper.SimpleCallback을 상속 받은 itemTouchCallback 객체를 만들었다.

  • ItemTouchHelper의 경우 공식 문서에 따르면 다음과 같다.

    이것은 RecyclerView에 해제 및 드래그 앤 드롭 지원을 위해 스와이프를 추가하는 유틸리티 클래스입니다.

    사용할 상호 작용 유형을 구성하고 사용자가 이러한 작업을 수행할 때 이벤트를 수신하는 RecyclerView 및 Callback 클래스와 함께 작동합니다.

위의 문서 내용과 같이 ItemTouchHelper.SimpleCallback 추상 클래스를 구현해줘야 하고,

여기서 사용한 onMove, onSwiped를 구현해줘야한다.

onMove, onSwiped는 각각 Drag, Swipe 될 때 호출되고 이때 RecyclerViewAdpater에 만들어 놓은 remomveData와 swipedData 함수를 사용했다.

 public ItemTouchHelper(@NonNull Callback callback) {
        mCallback = callback;
    }
public void attachToRecyclerView(@Nullable RecyclerView recyclerView)

그 뒤에
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerFollow)를 통해 recyclerView에 ItemTouchHelper를 적용시켜줬다.

profile
잘하고싶다
post-custom-banner

0개의 댓글