[android : kotlin] 코틀린 RecyclerView 아이템 클릭 리스너가 없다.(setOnItemClickListener , setOnLongClickListener) 처리 방법
리사이클러뷰(RecyclerView)에 대한 기본적인 사용방법은 아래 포스팅을 참고하면 되며, 이번에는 리사이클러뷰의 아이템(item)을 클릭했을 때 이벤트 처리 방법에 대해 알아보려 한다. 아래 포스팅에서 사용했던 코드를 사용하여 처리하려고 한다.
코틀린 RecyclerView는 setOnItemClickListener가 없다.
Listview에서 setOnItemClickListener 와 같은 기능을 지원하지 않습니다. addOnItemTouchListener와 같은 기능이 있지만 listview에서와 같이 직접적으로 position에 접근을 할 수 없고 여러 이슈가 있다고 합니다.
리스트뷰(ListView)는 메인 액티비티 코드에서 setOnItemClickListener 를 통해 각 item별 클릭 처리를 할 수 있다. 하지만 RecyclerView에서는 setOnItemClickListener()를 지원하지 않는다. 그럼으로 리스트뷰에서와 같이 직접적으로 posiiton에 접근할 수 없다. 왜그런것을까? 왜? 왜? 개발자를 귀찮게 하는건가?? 뭔가 문제가 있으니 지원을 안하는 것이 아닌가 생각이 되긴하는데….그 이유는 나중에 찾아 보자!!!
여튼 필요에 의해 setOnItemClickListener를 개발자가 임의로 구현해야 한다.
■첫번째 처리 방법으로 Adapter 클래스 생성시 리턴 람다식을 추가하는 방식이다.
package edu.kotlin.study import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView class CustomAdapter( private val context: Context, private val dataList: ArrayList<DataVo>, private val itemClick: (DataVo) -> Unit ) : RecyclerView.Adapter<CustomAdapter.ItemViewHolder>() { inner class ItemViewHolder(itemView: View, val itemClick: (DataVo) -> Unit) : RecyclerView.ViewHolder(itemView) { private val userPhoto = itemView.findViewById<ImageView>(R.id.userImg) private val userName = itemView.findViewById<TextView>(R.id.userNameTxt) private val userPay = itemView.findViewById<TextView>(R.id.payTxt) private val userAddress: TextView = itemView.findViewById<TextView>(R.id.addressTxt) fun bind(dataVo: DataVo, context: Context) { if (dataVo.photo != "") { val resourceId = context.resources.getIdentifier(dataVo.photo, "drawable", context.packageName) if (resourceId > 0) { userPhoto.setImageResource(resourceId) } else { userPhoto.setImageResource(R.mipmap.ic_launcher_round) } } else { userPhoto.setImageResource(R.mipmap.ic_launcher_round) } //TextView에 데이터 세팅 userName.text = dataVo.name userPay.text = dataVo.pay.toString() userAddress.text = dataVo.address itemView.setOnClickListener { itemClick(dataVo) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { val view = LayoutInflater.from(context).inflate(R.layout.view_item_layout, parent, false) return ItemViewHolder(view, itemClick) } override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { holder.bind(dataList[position], context) } override fun getItemCount(): Int { return dataList.size } }
MainActivity 클래스에서 클릭 이벤트 처리를 추가해주었다.
val mAdapter = CustomAdapter(this, userList) { dataVo -> //리스트가 클릭되었을 때 이벤트를 처리한다. textView1.text = "사용자명 : ${dataVo.name}" }
[MainActivity.kt] 전체 코드
package edu.kotlin.study import android.media.MediaPlayer import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { var userList = arrayListOf<DataVo>( DataVo("IU", "Male", "4", 30000000, "user_img_01"), DataVo("홍길동", "Female", "1", 10000000, "user_img_02"), DataVo("김영수", "Female", "3", 20000000, "user_img_03") ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // val mAdapter = CustomAdapter(this, userList) //recycler_view.adapter = mAdapter val mAdapter = CustomAdapter(this, userList) { dataVo -> //리스트가 클릭되었을 때 이벤트를 처리한다. textView1.text = "사용자명 : ${dataVo.name}" } recycler_view.adapter = mAdapter val layout = LinearLayoutManager(this) recycler_view.layoutManager = layout recycler_view.setHasFixedSize(true) } }
■두번째 방법으로 CustomAdapter에 클릭 인터페이스를 추가하여 처리하는 방식이다. 이렇게 처리하는게 개발자에게 낯설지 않고 이해하기가 더 빠르겠다. 첫번째 방식은 뭔가 난해하다.
1. 어댑터 내에 클릭 인터페이스(interface)를 정의한다.
2. 리스너를 lateinit 키워드를 사용하여 선언 해준다.
3. 메인 액티비티 클래스에서 오버라이드하여 처리할 수 있도록 초기화 메서드 setMyItemClickListener()를 추가한다.
4. inner class ItemViewHolder 에서 itemView의 OnClickListener를 상속받아 초기화 처리한다.
리사이클러뷰의 adapterPosition( getAdapterPosition() 같음) 메서드를 사용하여 클릭한 아이템의 position 값을 가져올 수 있다.
//초기화 init { itemView.setOnClickListener { mItemClickListener.onItemClick(adapterPosition) } itemView.setOnLongClickListener { mItemClickListener.onLongClick(adapterPosition) return@setOnLongClickListener true } }
package edu.kotlin.study import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView class CustomAdapter( private val context: Context, private val dataList: ArrayList<DataVo> ) : RecyclerView.Adapter<CustomAdapter.ItemViewHolder>() { //클릭 인터페이스를 정의. interface MyItemClickListener { fun onItemClick(position: Int) fun onLongClick(position: Int) } //클릭 리스너 선언 private lateinit var mItemClickListener: MyItemClickListener //클릭 리스너 등록 메서드 ( 메인 액티비티에서 람다식 혹은 inner 클래스로 호출) fun setMyItemClickListener(itemClickListener: MyItemClickListener) { mItemClickListener = itemClickListener } inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { //초기화 init { itemView.setOnClickListener { mItemClickListener.onItemClick(adapterPosition) } itemView.setOnLongClickListener { mItemClickListener.onLongClick(adapterPosition) return@setOnLongClickListener true } } private val userPhoto = itemView.findViewById<ImageView>(R.id.userImg) private val userName = itemView.findViewById<TextView>(R.id.userNameTxt) private val userPay = itemView.findViewById<TextView>(R.id.payTxt) private val userAddress: TextView = itemView.findViewById<TextView>(R.id.addressTxt) fun bind(dataVo: DataVo, context: Context) { if (dataVo.photo != "") { val resourceId = context.resources.getIdentifier(dataVo.photo, "drawable", context.packageName) if (resourceId > 0) { userPhoto.setImageResource(resourceId) } else { userPhoto.setImageResource(R.mipmap.ic_launcher_round) } } else { userPhoto.setImageResource(R.mipmap.ic_launcher_round) } //TextView에 데이터 세팅 userName.text = dataVo.name userPay.text = dataVo.pay.toString() userAddress.text = dataVo.address } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { val view = LayoutInflater.from(context).inflate(R.layout.view_item_layout, parent, false) return ItemViewHolder(view) } override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { holder.bind(dataList[position], context) } override fun getItemCount(): Int { return dataList.size } }
이제 메인엑티비티클래스에서 오버라이드를 처리하여 이벤트 처리를 진행한다.
package edu.kotlin.study import android.media.MediaPlayer import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { var userList = arrayListOf<DataVo>( DataVo("IU", "test1", "전주시", 30000000,"user_img_01"), DataVo("홍길동", "test2", "서울시",10000000, "user_img_02"), DataVo("김영수", "test3", "광주시", 20000000, "user_img_03") ) private var mediaPlayer: MediaPlayer? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mAdapter = CustomAdapter(this, userList) mAdapter.setMyItemClickListener(object : CustomAdapter.MyItemClickListener { override fun onItemClick(position: Int) { textView1.text = "사용자명 : ${userList[position].name}" } override fun onLongClick(position: Int) { textView1.text = "사용자명 : ${userList[position].address}" } }) recycler_view.adapter = mAdapter val mLayout = LinearLayoutManager(this) recycler_view.layoutManager = mLayout recycler_view.setHasFixedSize(true) } }
[build.gradle(Module:app)]
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 defaultConfig { applicationId "edu.kotlin.study" minSdkVersion 22 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }
[build.gradle(Project)]
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = "1.3.72" repositories { google() jcenter() } dependencies { classpath "com.android.tools.build:gradle:4.0.1" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
[REFERENCE]
https://developer.android.com/guide/topics/ui/layout/recyclerview.html#structure
stackoverflow.com/questions/29424944/recyclerview-itemclicklistener-in-kotlin
stackoverflow.com/questions/48642787/how-to-click-recyclerview-items-in-activity