관리 메뉴

나만을 위한 블로그

[Android] 코틀린으로 리사이클러뷰 클릭 이벤트 구현하는 방법 정리 본문

Android

[Android] 코틀린으로 리사이클러뷰 클릭 이벤트 구현하는 방법 정리

참깨빵위에참깨빵 2022. 8. 8. 02:56
728x90
반응형

리사이클러뷰 클릭 이벤트에 대해선 예전에 자바로 작성한 적이 있다.

 

https://onlyfor-me-blog.tistory.com/40

 

[Android] 리사이클러뷰 클릭 이벤트 2

참고한 사이트 : https://recipes4dev.tistory.com/168 리사이클러뷰에서도 리스트뷰처럼 setOnItemClickListener() 사용과 유사한 방법을 통해 아이템 클릭 이벤트를 처리할 수 있나? 디벨로퍼에는 이와 같은 or..

onlyfor-me-blog.tistory.com

 

그러나 난 지금 자바가 아닌 코틀린으로 안드로이드 앱을 만들고 있고, 위 포스팅에 적힌 방법 외의 다른 방법들을 설명한 미디엄 포스팅을 발견해서 나중에 필요할 때 보려고 포스팅한다.

먼저 아래의 모델 클래스를 만들어둔다.

 

data class Car(
    val brand: String,
    val model: Int,
    val isDiesel: Boolean
)

 

그리고 아이템 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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="model"
            type="com.example.kotlinprac.recyclerview.clickevent.Car" />
    </data>

    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        app:cardCornerRadius="5dp"
        app:cardElevation="10dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="20dp">

            <TextView
                android:id="@+id/tvName"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{model.brand}"
                tools:text="Jane" />

            <TextView
                android:id="@+id/tvAge"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{Integer.toString(model.model)}"
                tools:text="26" />

            <TextView
                android:id="@+id/tvSwim"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text='@{model.isDiesel ? "Yes" : "No"}'
                tools:text="Yes" />

        </LinearLayout>

    </com.google.android.material.card.MaterialCardView>

</layout>

 

액티비티의 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"
    xmlns:tools="http://schemas.android.com/tools">

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

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ActivityRecyclerViewClickEventBinding

class RecyclerViewClickEventActivity : AppCompatActivity() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: ActivityRecyclerViewClickEventBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_recycler_view_click_event)
        binding.run {
            //
        }
    }
}

 

1. 어댑터의 onBindViewHolder()에서 클릭 리스너 설정

 

가장 기본적인 방법이다. 그냥 어댑터 안의 onBindViewHolder()에서 클릭 리스너를 붙일 뿐이다.

import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinprac.databinding.RowItemBinding

class CarAdapter(
    private val carList: ArrayList<Car>,
): RecyclerView.Adapter<CarAdapter.CarViewHolder>() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: RowItemBinding

    inner class CarViewHolder(
        binding: RowItemBinding
    ): RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Car) {
            binding.model = item
        }
    }

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

    override fun onBindViewHolder(holder: CarViewHolder, position: Int) {
        val item = carList[position]
        holder.bind(item)

        binding.root.setOnClickListener {
            val brand = item.brand
            val model = item.model
            val isDiesel = item.isDiesel
            Log.e(TAG, "어댑터에서 brand : $brand, model : $model, isDiesel : $isDiesel")
        }
    }

    override fun getItemCount(): Int = carList.size
}

 

액티비티에서 적용해준다.

 

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ActivityRecyclerViewClickEventBinding

class RecyclerViewClickEventActivity : AppCompatActivity() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: ActivityRecyclerViewClickEventBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_recycler_view_click_event)
        binding.run {
            val carList = arrayListOf(
                Car(
                    brand = "브랜드1",
                    model = 0,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드2",
                    model = 1,
                    isDiesel = false
                ),
                Car(
                    brand = "브랜드3",
                    model = 2,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드4",
                    model = 3,
                    isDiesel = false
                ),
                Car(
                    brand = "브랜드5",
                    model = 4,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드6",
                    model = 5,
                    isDiesel = false
                ),
            )
            val carAdapter = CarAdapter(carList)
            recyclerView.apply {
                layoutManager = LinearLayoutManager(this@RecyclerViewClickEventActivity)
                setHasFixedSize(true)
                adapter = carAdapter
            }
        }
    }
}

 

이후 실행해서 아이템을 클릭하면 이런 형태로 로그가 찍힐 것이다.

 

 

간편하지만 액티비티 / 프래그먼트에서 클릭 이벤트를 구현해서 클릭한 아이템의 position을 알고 싶거나 한 경우에는 이 방식을 쓰면 후속 처리가 귀찮아진다. 어댑터 안에서 클릭 리스너를 처리해도 별 상관이 없는 경우에는 쓸만하지만 뷰모델의 API를 호출한다거나 할 때는 다른 방법을 쓰는 게 낫다.

 

2. 어댑터 생성자로 커스텀 람다 함수 넘기기

 

이 방법은 어댑터의 생성자에 리스트와 클릭 리스너를 넘긴다. 이 때 클릭 리스너는 Car 객체를 인자로 받아야 하며 리턴값이 필요없다면 Unit으로 설정한다.

어댑터 코드는 아래와 같다.

 

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinprac.databinding.RowItemBinding

class CarAdapter(
    private val carList: ArrayList<Car>,
    private val clickListener: (Car) -> Unit
): RecyclerView.Adapter<CarAdapter.CarViewHolder>() {

    private lateinit var binding: RowItemBinding

    inner class CarViewHolder(
        binding: RowItemBinding
    ): RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Car) {
            binding.model = item
        }
    }

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

    override fun onBindViewHolder(holder: CarViewHolder, position: Int) {
        val item = carList[position]
        holder.bind(item)

        binding.root.setOnClickListener {
            clickListener(item)
        }
    }

    override fun getItemCount(): Int = carList.size
}

 

그리고 이를 액티비티에서 적용한다.

 

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ActivityRecyclerViewClickEventBinding

class RecyclerViewClickEventActivity : AppCompatActivity() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: ActivityRecyclerViewClickEventBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_recycler_view_click_event)
        binding.run {
            val carList = arrayListOf(
                Car(
                    brand = "브랜드1",
                    model = 0,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드2",
                    model = 1,
                    isDiesel = false
                ),
                Car(
                    brand = "브랜드3",
                    model = 2,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드4",
                    model = 3,
                    isDiesel = false
                ),
                Car(
                    brand = "브랜드5",
                    model = 4,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드6",
                    model = 5,
                    isDiesel = false
                ),
            )
            val carAdapter = CarAdapter(carList) { car ->
                val brand = car.brand
                val model = car.model
                val isDiesel = car.isDiesel
                Log.e(TAG, "brand : $brand, model : $model, isDiesel : $isDiesel")
            }
            recyclerView.apply {
                layoutManager = LinearLayoutManager(this@RecyclerViewClickEventActivity)
                setHasFixedSize(true)
                adapter = carAdapter
            }
        }
    }
}

 

실행 후 화면에 나타난 아이템을 클릭하면 아래와 같이 로그가 출력될 것이다.

 

 

이렇게 어댑터를 구성할 경우 액티비티 / 프래그먼트에서 클릭 이벤트를 처리하고 싶은 경우 매우 간단하게 구현할 수 있는 장점이 있다. 리시버 이름도 it이 아니라 car 등 내가 원하는 이름을 지정할 수 있다.

 

3. 전역 람다 함수

 

이 방법은 2번 방법에서 확장된 방법이다. 2번 방법은 모든 어댑터, 아이템 타입에 대해 람다 함수를 작성해야 하지만 이 방법에선 제네릭 매개변수 T를 써서 새 인터페이스를 작성하는 것만 다르다.

먼저 인터페이스를 하나 만든다.

 

interface ItemClickListener<T> {
    fun onItemClick(pos: Int, item: T)
}

 

그리고 어댑터 파일에서 생성자로 해당 인터페이스의 참조변수를 넘겨받도록 설정하고 onBindViewHolder()에서 클릭 리스너를 설정한다.

 

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinprac.databinding.RowItemBinding

class CarAdapter(
    private val carList: ArrayList<Car>,
    private val clickListener: ItemClickListener<Car>
): RecyclerView.Adapter<CarAdapter.CarViewHolder>() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: RowItemBinding

    inner class CarViewHolder(
        binding: RowItemBinding
    ): RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Car) {
            binding.model = item
        }
    }

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

    override fun onBindViewHolder(holder: CarViewHolder, position: Int) {
        val item = carList[position]
        holder.bind(item)

        binding.root.setOnClickListener {
            clickListener.onItemClick(position, item)
        }
    }

    override fun getItemCount(): Int = carList.size
}

 

액티비티에서 구현은 아래처럼 한다. 참고로 인터페이스에서 만든 형식대로 object : ItemClickListener 오른쪽에 타입을 명시하지 않으면 컴파일 에러가 발생하기 때문에 꼭 넣어줘야 한다.

 

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ActivityRecyclerViewClickEventBinding

class RecyclerViewClickEventActivity : AppCompatActivity() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: ActivityRecyclerViewClickEventBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_recycler_view_click_event)
        binding.run {
            val carList = arrayListOf(
                Car(
                    brand = "브랜드1",
                    model = 0,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드2",
                    model = 1,
                    isDiesel = false
                ),
                Car(
                    brand = "브랜드3",
                    model = 2,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드4",
                    model = 3,
                    isDiesel = false
                ),
                Car(
                    brand = "브랜드5",
                    model = 4,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드6",
                    model = 5,
                    isDiesel = false
                ),
            )
            val carAdapter = CarAdapter(carList, object : ItemClickListener<Car> {
                override fun onItemClick(pos: Int, item: Car) {
                    val brand = item.brand
                    val model = item.model
                    val isDiesel = item.isDiesel
                    Log.e(TAG, "brand : $brand, model : $model, isDiesel : $isDiesel")
                }
            })
            recyclerView.apply {
                layoutManager = LinearLayoutManager(this@RecyclerViewClickEventActivity)
                setHasFixedSize(true)
                adapter = carAdapter
            }
        }
    }
}

 

실행하면 역시 잘 동작한다.

 

 

이 방법의 경우 동일한 매개변수가 필요한 클릭 이벤트 콜백이 필요할 때 인터페이스를 만들고 그 안에 함수를 정의해서, 만드는 어댑터마다 적용할 수 있어 불필요한 코드를 쓰지 않아도 된다는 장점이 있다. 다른 모델 클래스를 사용하더라도 그 이름만 바꿔서 넣어주면 되기 때문에 확장성도 가져갈 수 있다.

 

4. 커스텀 인터페이스

 

이 방법은 예전에 자바로 작성했던 포스트와 비슷한 방법이다. 어댑터 내부에서 인터페이스와 추상 함수를 정의한 다음 onBindViewHolder() 안의 클릭 이벤트에서 호출하고, 액티비티 / 프래그먼트에서 이 인터페이스를 구현하는 방법이다.

 

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinprac.databinding.RowItemBinding

class CarAdapter(
    private val carList: ArrayList<Car>,
    private val clickListener: OnCarClickListener
): RecyclerView.Adapter<CarAdapter.CarViewHolder>() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: RowItemBinding

    inner class CarViewHolder(
        binding: RowItemBinding
    ): RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Car) {
            binding.model = item
        }
    }

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

    override fun onBindViewHolder(holder: CarViewHolder, position: Int) {
        val item = carList[position]
        holder.bind(item)

        binding.root.setOnClickListener {
            clickListener.onCarClick(position, item)
        }
    }

    override fun getItemCount(): Int = carList.size

    interface OnCarClickListener {
        fun onCarClick(position: Int, car: Car)
    }

}
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ActivityRecyclerViewClickEventBinding

class RecyclerViewClickEventActivity : AppCompatActivity() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: ActivityRecyclerViewClickEventBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_recycler_view_click_event)
        binding.run {
            val carList = arrayListOf(
                Car(
                    brand = "브랜드1",
                    model = 0,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드2",
                    model = 1,
                    isDiesel = false
                ),
                Car(
                    brand = "브랜드3",
                    model = 2,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드4",
                    model = 3,
                    isDiesel = false
                ),
                Car(
                    brand = "브랜드5",
                    model = 4,
                    isDiesel = true
                ),
                Car(
                    brand = "브랜드6",
                    model = 5,
                    isDiesel = false
                ),
            )
            val carAdapter = CarAdapter(carList, object : CarAdapter.OnCarClickListener {
                override fun onCarClick(position: Int, car: Car) {
                    val brand = car.brand
                    val model = car.model
                    val isDiesel = car.isDiesel
                    Log.e(TAG, "${position}번 아이템 클릭 - brand : $brand, model : $model, isDiesel : $isDiesel")
                }
            })
            recyclerView.apply {
                layoutManager = LinearLayoutManager(this@RecyclerViewClickEventActivity)
                setHasFixedSize(true)
                adapter = carAdapter
            }
        }
    }
}

 

이 방식으로 만든 리사이클러뷰 클릭 이벤트 또한 아래처럼 작동한다.

 

 

다른 방법을 쓰더라도 액티비티 / 프래그먼트에서 클릭한 아이템의 position 값은 구할 수 있다. 또한 각 방식마다 장단점이 있긴 하지만 제일 좋은 건 자신의 상황에 맞고 빠른 속도로 정확하게 작동하는 리사이클러뷰 클릭 이벤트 구현 방법을 적용하는 것이다.

각 방법들을 한 번이라도 자신의 프로젝트에 붙이면서 연습한 다음 자신이 제일 좋다고 판단한 or 적절한 방법을 사용하자.

반응형
Comments