본문 바로가기
Android Application/응용 구현

안드로이드 리사이클러뷰 아이템 클릭(recyclerView item click)처리

by sdchjjj 2023. 4. 4.
728x90

안녕하세요.

이번 포스팅에서는 리사이클러뷰의 아이템의 클릭 이벤트를 처리하는 기능을 구현해 보겠습니다.

 

언어: 코틀린

sdk vsersion

  - compile: 33

  - min: 21

  - target: 33

 

가장 먼저 예제에 쓰일 아이템을 만들겠습니다.

RecyclerViewItemProvider.kt

object RecyclerViewItemProvider {
    fun getItems(): List<Animal> {
        return listOf(
            Animal("포유류", "고양이"),
            Animal("포유류", "강아지"),
            Animal("포유류", "토끼"),
            Animal("포유류", "미어캣"),
            Animal("조류", "팽귄"),
            Animal("조류", "참새"),
            Animal("어류", "잉어"),
            Animal("파충류", "악어"),
            Animal("파충류", "도마뱀"),
            Animal("어류", "금붕어"),
        )
    }
}

data class Animal(
    val species: String,
    val name: String
)

 

recycler view의 item view입니다.

view_animal_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="animal"
            type="com.contents.laboratory.Animal" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="5dp">

        <TextView
            android:id="@+id/text_species"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{animal.species}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" />

        <TextView
            android:id="@+id/text_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{animal.name}"
            app:layout_constraintTop_toBottomOf="@id/text_species"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

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

데이터 바인딩을 사용해 text view를 표시하도록 합니다.

 

프래그먼트와 뷰모델을 생성합니다.

RecyclerViewClickViewModel.kt

@HiltViewModel
class RecyclerViewClickViewModel @Inject constructor() : ViewModel() {

    private val _clickedItem = MutableLiveData<Animal>()
    val clickedItem: LiveData<Animal> get() = _clickedItem

    fun onItemClicked(item: Animal) {
        _clickedItem.postValue(item)
    }
}

 

RecyclerViewClickFragment.kt

@AndroidEntryPoint
class RecyclerViewClickFragment : Fragment() {

    private var _binding: FragmentRecyclerViewClickBinding? = null
    private val binding get() = _binding!!

    private val viewModel: RecyclerViewClickViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentRecyclerViewClickBinding.inflate(layoutInflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

 

fragment_recycler_view_click.xml

<?xml version="1.0" encoding="utf-8"?>
<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>

        <variable
            name="viewModel"
            type="com.contents.laboratory.RecyclerViewClickViewModel" />

    </data>

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

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/animal_recycler_view"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            app:layout_constraintStart_toStartOf="parent"
            tools:listitem="@layout/view_animal_item" />

        <TextView
            android:id="@+id/text_species"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.clickedItem.species}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

        <TextView
            android:id="@+id/text_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.clickedItem.name}"
            app:layout_constraintTop_toBottomOf="@id/text_species"
            app:layout_constraintEnd_toEndOf="parent" />

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

데이터 바인딩을 사용해 클릭한 아이템이 ui에 표시되도록 작성했습니다.

 

어답터를 작성하겠습니다.

AnimalAdapter.kt

class AnimalAdapter : ListAdapter<Animal, AnimalAdapter.AnimalViewHolder?>(
    AnimalDataCallback
) {

    class AnimalViewHolder(
        private val binding: ViewAnimalItemBinding,
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Animal) {
            binding.animal = item
        }
    }

    companion object {
        private val AnimalDataCallback = object : DiffUtil.ItemCallback<Animal>() {
            override fun areItemsTheSame(
                oldItem: Animal,
                newItem: Animal
            ): Boolean {
                return oldItem.hashCode() == newItem.hashCode()
            }

            override fun areContentsTheSame(
                oldItem: Animal,
                newItem: Animal
            ): Boolean {
                return oldItem == newItem
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimalViewHolder {
        val binding =
            ViewAnimalItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        return AnimalViewHolder(binding)
    }

    override fun onBindViewHolder(holder: AnimalViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
}

여기에 click listener를 추가하겠습니다.

 

Adapter 파일 내에 ClickEventListener class를 추가하며, click event 발생 시 animal을 인자로 넘기도록 합니다.

AnimalAdapter.kt

class AnimalClickListener(val clickListener: (animal: Animal) -> Unit) {
    fun onClick(animal: Animal) = clickListener(animal)
}

 

adapater 전체 소스입니다.

AnimalAdapter.kt

class AnimalAdapter(
    private val animalClickListener: AnimalClickListener
) : ListAdapter<Animal, AnimalAdapter.AnimalViewHolder?>(
    AnimalDataCallback
) {

    class AnimalViewHolder(
        private val binding: ViewAnimalItemBinding,
        private val animalClickListener: AnimalClickListener
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Animal) {
            binding.animal = item
            binding.clickListener = animalClickListener
        }
    }

    companion object {
        private val AnimalDataCallback = object : DiffUtil.ItemCallback<Animal>() {
            override fun areItemsTheSame(
                oldItem: Animal,
                newItem: Animal
            ): Boolean {
                return oldItem.hashCode() == newItem.hashCode()
            }

            override fun areContentsTheSame(
                oldItem: Animal,
                newItem: Animal
            ): Boolean {
                return oldItem == newItem
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimalViewHolder {
        val binding =
            ViewAnimalItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        return AnimalViewHolder(binding, animalClickListener)
    }

    override fun onBindViewHolder(holder: AnimalViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
}

class AnimalClickListener(val clickListener: (animal: Animal) -> Unit) {
    fun onClick(animal: Animal) = clickListener(animal)
}

 

에러가 발생할 아래의 코드는 이후에 item xml을 작업해 주면 해결됩니다.

binding.clickListener = animalClickListener

 

item view에 clickListener를 추가하고, onClick을 설정해 줍니다.

view_animal_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="animal"
            type="com.contents.laboratory.Animal" />

        <variable
            name="clickListener"
            type="com.contents.laboratory.AnimalClickListener" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="5dp"
        android:onClick="@{() -> clickListener.onClick(animal)}">

        <TextView
            android:id="@+id/text_species"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{animal.species}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" />

        <TextView
            android:id="@+id/text_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{animal.name}"
            app:layout_constraintTop_toBottomOf="@id/text_species"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

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

클릭 이벤트를 발생시킬 준비가 끝났습니다.

 

이제 프래그먼트에 어답터를 추가하겠습니다.

RecyclerViewClickFragment.kt

@AndroidEntryPoint
class RecyclerViewClickFragment : Fragment() {

    private var _binding: FragmentRecyclerViewClickBinding? = null
    private val binding get() = _binding!!

    private val viewModel: RecyclerViewClickViewModel by viewModels()

    private val animalAdapter by lazy {
        AnimalAdapter(AnimalClickListener {
            viewModel.onItemClicked(it)
        })
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentRecyclerViewClickBinding.inflate(layoutInflater, container, false).apply {
            viewModel = this@RecyclerViewClickFragment.viewModel
            lifecycleOwner = viewLifecycleOwner
        }
        binding.animalRecyclerView.apply {
            layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
            adapter = animalAdapter
        }
        animalAdapter.submitList(RecyclerViewItemProvider.getItems().toMutableList())
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

아이템 클릭 시 뷰모델의 함수를 호출하며 아이템을 인자로 넘기고, 뷰모델에서 해당 아이템을 post합니다.

 

에뮬레이터에서 실행해 보겠습니다.

figure1. run

정상적으로 동작합니다.

 

이상 포스팅을 마치겠습니다.

 

감사합니다.

 

728x90

댓글