깨알 개념/Android

[Android] RecyclerView - Adapter에 인자(Argument) 전달

interfacer_han 2024. 2. 3. 14:24

#1 이전 글

 

[Android] RecyclerView - 기초

#1 ListView vs RecyclerView 화면에 요소(Item)을 100개 표현한다고 해보자. ListView나 GridView 등의 전통적인 Container Widget들은 이 100개의 아이템을 모두 불러온(load)다. 그리고 화면을 스크롤하면 미리 Load되

kenel.tistory.com

이전 글의 완성된 앱이, Adapter가 인자(Argument)를 전달받도록 수정해본다. 추가로, ViewHolder에 클릭 리스너를 구현하는 예시와 그 클릭 리스너 또한 인자(Argument)로 전달하는 코드도 살펴본다.
 

#2 Adapter가 인자를 전달받게 만들기

#2-1 Menu.kt 만들기

// package com.example.argumenttoadapter

data class Menu(val name: String, val price: Int)

먼저, Adapter에 인자(Argument)로서 전달될 data class를 만든다.
 

#2-2 MainActivity.kt 수정

...

class MainActivity : AppCompatActivity() {
    val burgers = listOf<Menu>(
        Menu("Classic Cheeseburger", 6800),
        Menu("Bacon Deluxe Burger", 7800),
        Menu("BBQ Ranch Burger", 5800),
        Menu("Mushroom Swiss Burger", 6700),
        Menu("Double Stack Burger", 9800),
        Menu("Spicy Chicken Burger", 7900),
        Menu("Crispy Chicken Burger", 6800),
        Menu("Grilled Chicken Club", 9700),
        Menu("Veggie Burger Deluxe", 6800),
        Menu("Hawaiian Teriyaki Burger", 8900),
        Menu("Philly Cheese Steak Burger", 6200),
        Menu("Western BBQ Burger", 7300),
        Menu("Jalapeno Pepper Jack Burger", 7900),
        Menu("Turkey Avocado Burger", 6700),
        Menu("Black Bean Burger", 7600),
        Menu("Bacon Jalapeno Burger", 6700),
        Menu("Ultimate BBQ Bacon Burger", 6700),
        Menu("Chicken Bacon Ranch Burger", 6700),
        Menu("Chipotle Guacamole Burger", 6700),
        Menu("Breakfast Burger", 4700),
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        recyclerView.adapter = MyRecyclerViewAdapter(burgers)
    }
}

Menu 객체 20개를 담은 List를 만들고, Adapter의 생성자 안에 넣어준다.
 

#2-3 MyRecyclerViewAdapter.kt 수정

..

class MyRecyclerViewAdapter(val menus: List<Menu>) : RecyclerView.Adapter<MyViewHolder>() {
    ...

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

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
/*
        // 나쁘지 않지만 객체지향적이지는 않은 코드
        val menu = menus[position]
        holder.textItem.text = "${menu.name}\n${menu.price}₩"
*/
        // 객체지향적인 코드
        holder.bind(menus[position])
    }
}

class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val textItem: TextView = view.findViewById(R.id.textItem)

    fun bind(menu: Menu) {
        textItem.text = "${menu.name}\n${menu.price}₩"
    }
}

Adapter가 List<Menu>를 받도록 생성자를 수정한다. 그리고 해당 List<Menu> 매개변수를 Adapter의 프로퍼티로서 활용할 것이므로 val 키워드도 붙인다.
 
getItemCount()가 인자로 받은 리스트의 길이를 출력하게 수정한다. ViewHolder에 자기 자신을 Bind하는 메소드를 만들고,
onBindViewHolder에선 position 매개변수를 이용해 ViewHolder.Bind()를 작동시키게 만든다.
 

#2-4 작동 확인

 

#3 ViewHolder에 클릭 리스너 달기

#3-1 기본적인 구현

...

class MyRecyclerViewAdapter(val menus: List<Menu>) : RecyclerView.Adapter<MyViewHolder>() {
    ...
}

class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
    val textItem: TextView = view.findViewById(R.id.textItem)

    fun bind(menu: Menu) {
        textItem.text = "${menu.name}\n${menu.price}₩"

        view.setOnClickListener {
            Toast.makeText(
                view.context, "Selected Menu is ${menu.name}", Toast.LENGTH_LONG
            ).show()
        }
    }
}

MyViewHolder의 매개변수 view를 MyViewHolder의 프로퍼티로 활용할 것이므로 val을 붙인다. 그리고 MyViewHolder.bind()에 클릭 리스너를 등록한다.
 

#3-2 인자(Argument)로 전달받아 구현

...

class MainActivity : AppCompatActivity() {
    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        recyclerView.adapter = MyRecyclerViewAdapter(burgers) { menu: Menu -> itemClicked(menu) }
    }

    private fun itemClicked(menu: Menu) {
        Toast.makeText(
            this, "Selected Menu is ${menu.name}", Toast.LENGTH_LONG
        ).show()
    }
}

#3-1에서 만들었던 Toast 메시지 코드를 itemClicked()라는 메소드를 만들어 그 안으로 옮긴다. 그리고 #2-2에서와 같은 맥락으로 Adapter의 생성자에 itemClicked()를 람다식의 형태로 넣는다. 코틀린에서 함수는 일급 객체이기에 인자(Argument)로서 전달될 수 있다. 그래서 가능한 코드다. 람다식에 관한 문법은 여기에서 확인하자. 람다식이 생성자의 마지막 인자이기에, 편의성 문법(이 링크의 #2-5 참조)을 적용해 괄호 밖으로 뺀 모습이다.
 

...

class MyRecyclerViewAdapter(val menus: List<Menu>, val clickListener: (Menu) -> Unit) :
    ...

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        ...
        holder.bind(menus[position], clickListener)
    }
}

class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
    val textItem: TextView = view.findViewById(R.id.textItem)

    fun bind(menu: Menu, clickListener: (Menu) -> Unit) {
        textItem.text = "${menu.name}\n${menu.price}₩"

        view.setOnClickListener {
            clickListener(menu)
        }
    }
}

Adapter가 (Menu) -> Unit 형 함수를 받도록 생성자를 수정한다. 그리고 해당 (Menu) -> Unit형 매개변수를 Adapter의 프로퍼티로서 활용할 것이므로 val 키워드도 붙인다.
 
MyViewHolder.bind()가 MyRecyclerViewAdapter.clickListener를 받도록 수정하고 view.setOnClickListener에 해당 함수를 할당한다.
 

#3-3 작동 확인 (#3-1 및 #3-2)

 

#4 요약

코틀린의 함수가 일급 객체임을 알고 이해해야, 제대로 활용할 수 있다.
 

#5 완성된 앱

https://github.com/Kanmanemone/android-practice/tree/master/recycler-view/ArgumentToAdapter