마지막 수정 : 2018-06-29

RecyclerView(1) 포스트에서는 리사이클러뷰를 통해 데이터를 출력하는 과정까지 정리했다. 이번 포스트에서는 코틀린 프로젝트에서 각 item을 클릭했을 때에 이벤트를 처리하는 방법을 정리했다.

itemClick Listener

ListView에서는 메인 액티비티 코드에서 setOnClickListener를 통해 각 item별 클릭 처리를 할 수 있었지만, RecyclerView에서는 별도로 클릭 리스너를 생성해주어야 한다. 이 처리는 Adapter에서 람다를 통해 설정해주었다.

지난 포스트에 이어서, 예제는 강아지 목록을 나타내는 화면이다.


아래 코드는 Dog 목록 화면을 나타낼 RecyclerView의 Adapter이다. 맨 윗줄에서 어댑터에 대한 constructor로 context 와, 커스텀클래스 Dog의 변수를 모아둔 ArrayList인 dogList 을 사용한다.

/* MainRvAdapter.kt */

class MainRvAdapter(val context: Context, val dogList: ArrayList<Dog>) :
RecyclerView.Adapter<MainRvAdapter.Holder>() {
    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder {
        val view = LayoutInflater.from(context).inflate(R.layout.main_rv_item, parent, false)
        return Holder(view)
    }

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

    override fun onBindViewHolder(holder: Holder?, position: Int) {
        holder?.bind(dogList[position], context)
    }

    inner class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
        val dogPhoto = itemView?.findViewById<ImageView>(R.id.dogPhotoImg)
        val dogBreed = itemView?.findViewById<TextView>(R.id.dogBreedTv)
        val dogAge = itemView?.findViewById<TextView>(R.id.dogAgeTv)
        val dogGender = itemView?.findViewById<TextView>(R.id.dogGenderTv)

        fun bind (dog: Dog, context: Context) {
            if (dog.photo != "") {
                val resourceId = context.resources.getIdentifier(dog.photo, "drawable", context.packageName)
                dogPhoto?.setImageResource(resourceId)
            } else {
                dogPhoto?.setImageResource(R.mipmap.ic_launcher)
            }
            dogBreed?.text = dog.breed
            dogAge?.text = dog.age
            dogGender?.text = dog.gender
        }
    }
}



코틀린에서는 람다의 이용이 가능하다. Dog를 파라미터로 받아서, 아무것도 반환하지 않는 파라미터를 람다식으로 나타낼 것이다. 그 생김새는 아래와 같다.

val itemClick : (Dog) -> Unit


Dog를 파라미터로 사용하고, 아무것도 반환하지 않는(Unit) 기능을 하는 함수 자체를 itemClick 변수에 넣는다. 그 itemClick 변수를 Adapter의 파라미터로 넣고, 어댑터 내에서 setOnClickListener 기능을 설정할 때 (Dog) -> Unit에 해당하는 함수 자체를 하나의 변수로 꺼내 쓸 수 있는 것이다.

class MainRvAdapter(val context: Context, val dogList: ArrayList<Dog>, val itemClick: (Dog) -> Unit) : RecyclerView.Adapter<MainRvAdapter.Holder>() {
/* (1) Adapter의 파라미터에 람다식 itemClick을 넣는다. */

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder {
        val view = LayoutInflater.from(context).inflate(R.layout.main_rv_item, parent, false)
        return Holder(view, itemClick)
        /* (4) Holder의 파라미터가 하나 더 추가됐으므로, 이곳에도 추가해준다. */
    }

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

    override fun onBindViewHolder(holder: Holder?, position: Int) {
        holder?.bind(dogList[position], context)
    }

    inner class Holder(itemView: View?, itemClick: (Dog) -> Unit) : RecyclerView.ViewHolder(itemView) {
      /* (2) Holder에서 클릭에 대한 처리를 할 것이므로, Holder의 파라미터에 람다식 itemClick을 넣는다. */

        val dogPhoto = itemView?.findViewById<ImageView>(R.id.dogPhotoImg)
        val dogBreed = itemView?.findViewById<TextView>(R.id.dogBreedTv)
        val dogAge = itemView?.findViewById<TextView>(R.id.dogAgeTv)
        val dogGender = itemView?.findViewById<TextView>(R.id.dogGenderTv)

        fun bind (dog: Dog, context: Context) {
            if (dog.photo != "") {
                val resourceId = context.resources.getIdentifier(dog.photo, "drawable", context.packageName)
                dogPhoto?.setImageResource(resourceId)
            } else {
                dogPhoto?.setImageResource(R.mipmap.ic_launcher)
            }
            dogBreed?.text = dog.breed
            dogAge?.text = dog.age
            dogGender?.text = dog.gender

            itemView.setOnClickListener { itemClick(dog) }
            /* (3) itemView가 클릭됐을 때 처리할 일을 itemClick으로 설정한다.
             (Dog) -> Unit 에 대한 함수는 나중에 MainActivity.kt 클래스에서 작성한다. */
        }
    }
}


Adapter에서 itemClick에 대한 설정이 끝났으면 MainActivity로 돌아와서 람다에서 실행할 코드를 입력한다. 이 예제에서는 item을 클릭하면 간단하게 Toast를 띄우는 것으로 액션을 설정했지만, intent를 통해 다른 액티비티로 전환하는 등 다양한 액션을 취할 수 있다.

/* MainActivity.kt */

class MainActivity : AppCompatActivity() {

    var dogList = arrayListOf<Dog>(
            Dog("Chow Chow", "Male", "4", "dog00"),
            Dog("Breed Pomeranian", "Female", "1", "dog01"),
            Dog("Golden Retriver", "Female", "3", "dog02"),
            Dog("Yorkshire Terrier", "Male", "5", "dog03"),
            Dog("Pug", "Male", "4", "dog04"),
            Dog("Alaskan Malamute", "Male", "7", "dog05"),
            Dog("Shih Tzu", "Female", "5", "dog06")
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val mAdapter = MainRvAdapter(this, dogList) { dog ->
            Toast.makeText(this, "개의 품종은 ${dog.breed} 이며, 나이는 ${dog.age}세이다.", Toast.LENGTH_SHORT).show()
        }
        /* 람다식{(Dog) -> Unit} 부분을 추가하여 itemView의 setOnClickListener 에서 어떤 액션을 취할 지 설정해준다. */

        mRecyclerView.adapter = mAdapter

        val lm = LinearLayoutManager(this)
        mRecyclerView.layoutManager = lm
        mRecyclerView.setHasFixedSize(true)
    }
}


클릭 리스너가 완성됐다. 빌드하여 실행 후, 목록 중 하나를 클릭하면 다음과 같이 Toast 메시지가 나온다.