[Kotlin 개념] 리사이클러뷰 (RecyclerView)

이다을·2023년 9월 12일
2
post-custom-banner

리사이클러뷰 (RecyclerView)

뷰 바인딩을 사용하여 XML 레이아웃 파일과 액티비티를 연결합니다.

1. RecyclerView란?

RecyclerView는 안드로이드 앱에서 리스트 형태의 데이터를 표시하는데 사용되는 위젯입니다. 여러 아이템을 스크롤 가능한 리스트로 표현하며, 많은 아이템을 효율적으로 관리하고 보여주는 역할을 합니다.

  • Recyle → 재활용하다.
  • View를 재활용하여 사용하는 방법
  • ListView와 RecyclerView의 차이점
    • ListView
    • 스크롤 할 때마다 위에 있던 아이템은 삭제되고, 맨 아래 아이템은 생성 되길 반복합니다.
    • 아이템 개수 만큼 삭제와 생성을 반복하여 성능에 좋지 않습니다.
    • RecyclerView
    • 스크롤 할 때마다 위에 있던 아이템이 재활용 되어 아래로 이동하여 재사용 됩니다.
    • 예를 들어 10개 정도 View를 만들고 10개를 재활용하여 사용합니다.
    • View를 계속 만드는 ListView의 단점을 보완할 수 있습니다.

2. RecyclerView 기본 구조

  • Adapter
    • 데이터를 목록 형태로 보여주기 위해 사용됩니다.
    • 데이터를 아이템 뷰와 연결하는 역할을 합니다.
    • 데이터와 RecyclerView 사이의 통신을 위한 연결체 입니다.
  • ViewHolder
    • 아이템 뷰를 보유(저장)하고 표시하는 역할을 합니다.
    • 스크롤 해서 위로 올라간 View를 재활용하기 위해 View를 기억하는 역할을 합니다.
  • LayoutManager ⭐
    • 데이터나 아이템들이 리사이클러뷰 내부에서 배치되는 형태(방식)을 관리합니다.
    • 종류
    • LinearLayoutManager : 수평, 수직으로 배치 시켜줍니다.
    • GridLayoutManager : 그리드 화면으로 배치 시켜줍니다.
    • StaggeredGridLayoutManager : 높이가 불규칙한 그리드 화면으로 배치 시켜줍니다.

3. LayoutManager 정렬 방식

  • LinearLayoutManager : 수직
binding.recycelrView.layoutManager = LinearLayoutManager(this)
  • LinearLayoutManager : 수평
  • true : 순서 / false : 역순서
binding.recycelrView.layoutManager = LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false)
  • GridLayoutManager : 수직
binding.recycelrView.layoutManager = GridLayoutManager(this,3)
  • GridLayoutManager : 수평
binding.recycelrView.layoutManager = GridLayoutManager(this,3,GridLayoutManager.HORIZONTAL,false)
  • StaggeredGridLayoutManager
  • VERTICAL : 수직 / HORIZONTAL : 수평
  • 각 항목의 크기 별로 불규칙한 그리드 형식
binding.recycelrView.layoutManager = StaggeredGridLayoutManager(3,LinearLayoutManager.VERTICAL)

4. View Binding이란?

  • 상호 작용하는 코드를 쉽게 작성할 수 있습니다.
  • Layout에 ID가 있는 뷰에 직접 참조를 할 수 있습니다.
  • 대부분의 뷰 바인딩은 findViewById를 대체합니다.
    • 보일러 플레이트 코드 개선
    • 변수 선언 없이 바로 뷰에 접근할 수 있습니다.
  • 장점
    • View를 직접 참조해 없는 id를 참조할 일이 없어 (NPE)Null Pointer Exception이 발생하지 않습니다.
    • View에 잘못된 타입을 지정할 때 생기는 Class Cast Exception이 발생하지 않습니다.
    • 코드 가독성이 올라갑니다.

5. RecyclerView 사용 단계

1. 의존성 추가: build.gradle 파일에 RecyclerView 의존성을 추가합니다.

implementation ("androidx.recyclerview:recyclerview:1.3.1")

gradle 설정

android{
	...
	// AndroidStudio 3.6 ~ 4.0
	viewBinding{
	enabled = true
}
	// AndroidStudio 4.0 ~
	buildFeatures{
	viewBinding = true
	}
}
  1. 아이템 레이아웃 생성: 아이템 하나의 레이아웃을 작성합니다.
  2. Adapter 생성: RecyclerView.Adapter 클래스를 상속한 Adapter 클래스를 생성합니다.
  3. ViewHolder 생성: RecyclerView.ViewHolder 클래스를 상속한 ViewHolder 클래스를 생성합니다.
  4. LayoutManager 설정: RecyclerView에 사용할 레이아웃 매니저를 설정합니다.

👉 Adapter 및 ViewHolder : Basic

// **MyAdapter.kt**
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

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

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

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

    inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(item: String) {
            itemView.textView.text = item
        }
    }
}

👉 RecyclerView 및 LayoutManager 설정 : Basic

// MainActivity
class MainActivity : AppCompatActivity() {
		
		private lateinit var binding:ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
				
				// XML 뷰를 객체
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val items = listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5")
        val adapter = MyAdapter(items)
        binding.recyclerView.adapter = adapter
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
				
				// ID에 접근하여 사용
				binding.myButton.setOnClickListener{
            binding.myTextView.text = "바인딩이 잘 되네요!"
        }

    }
}

👉 Adapter 및 ViewHolder : Item Click

// **MyAdapter.kt**
class MyAdapter(val mItems: MutableList<MyItem>) : RecyclerView.Adapter<MyAdapter.Holder>() {

    interface ItemClick {
        fun onClick(view : View, position : Int)
    }

    var itemClick : ItemClick? = null

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

    override fun onBindViewHolder(holder: Holder, position: Int) {
        holder.itemView.setOnClickListener {  
            itemClick?.onClick(it, position)
        }
        holder.iconImageView.setImageResource(mItems[position].aIcon)
        holder.name.text = mItems[position].aName
        holder.age.text = mItems[position].aAge
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

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

    inner class Holder(val binding: ItemRecyclerviewBinding) : RecyclerView.ViewHolder(binding.root) {
        val iconImageView = binding.iconItem
        val name = binding.textItem1
        val age = binding.textItem2
    }
}

👉 RecyclerView 및 LayoutManager 설정 : Item Click

- 어댑터 객체에서 관리할 더미 데이터 객체(ArrayList)를 준비한다.
- MyAdapter 객체를 생성하고 초기화 한다.
- 생성된 MyAdapter 객체를 어댑터뷰인 리스트뷰에 연결한다.
// MainActivity
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

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

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val dataList = mutableListOf<MyItem>()
        dataList.add(MyItem(R.drawable.sample_0, "Bella", "1"))
        dataList.add(MyItem(R.drawable.sample_1, "Charlie", "2"))
        dataList.add(MyItem(R.drawable.sample_2, "Daisy", "1.5"))
        dataList.add(MyItem(R.drawable.sample_3, "Duke", "1"))
        dataList.add(MyItem(R.drawable.sample_4, "Max", "2"))
        dataList.add(MyItem(R.drawable.sample_5, "Happy", "4"))
        dataList.add(MyItem(R.drawable.sample_6, "Luna", "3"))
        dataList.add(MyItem(R.drawable.sample_7, "Bob", "2"))

        binding.recyclerView.adapter = MyAdapter(dataList)
				
				// 어댑터 생성, 연결
        val adapter = MyAdapter(dataList)
        binding.recyclerView.adapter = adapter
        binding.recyclerView.layoutManager = LinearLayoutManager(this) // 수직 배치

        adapter.itemClick = object : MyAdapter.ItemClick {
            override fun onClick(view: View, position: Int) {
                val name: String = dataList[position].aName
                Toast.makeText(this@MainActivity," $name 선택!", Toast.LENGTH_SHORT).show()
            }
        }
    }

👉 Data Class : Multi-view Type

// DogItems.kt
sealed class DogItems {
    data class MyItem(val aIcon:Int, val aName:String, val aAge:String) : DogItems()
    data class MyTitle(val age: String) : DogItems()
}

👉 Adapter 및 ViewHolder : Multi-view Type

// MyAdapter.kt
class MyAdapter(val mItems: MutableList<DogItems>) : RecyclerView.Adapter<ViewHolder>() {

    companion object {
        private const val VIEW_TYPE_TITLE = 1
        private const val VIEW_TYPE_DOGS = 2
    }

    interface StickyHeader {
        val stickyId: Any
    }

    interface ItemClick {
        fun onClick(view: View, position: Int)
    }

    var itemClick: ItemClick? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {

        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            VIEW_TYPE_TITLE -> {
                val binding =
                    ItemTitleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                TitleViewHolder(binding)
            }
            else -> {
                val binding =
                    ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                DogViewHolder(binding)
            }
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        when (val item = mItems[position]) {
            is DogItems.MyTitle -> {
                (holder as TitleViewHolder).title.text = "${item.age} 살"
            }
            is DogItems.MyItem -> {
                (holder as DogViewHolder).name.text = item.aName
                holder.age.text = item.aAge
                holder.iconImageView.setImageResource(item.aIcon)

                holder.itemView.setOnClickListener {
                    itemClick?.onClick(it, position)
                }
            }
        }
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

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

    override fun getItemViewType(position: Int): Int {
        return when (mItems[position]) {
            is DogItems.MyTitle -> VIEW_TYPE_TITLE
            is DogItems.MyItem -> VIEW_TYPE_DOGS
        }
    }

    inner class TitleViewHolder(binding: ItemTitleBinding) : RecyclerView.ViewHolder(binding.root), StickyHeader {
        val title = binding.tvAgetitle

        override val stickyId: String
            get() = (mItems[adapterPosition] as DogItems.MyTitle).age
    }

    inner class DogViewHolder(binding: ItemRecyclerviewBinding) :
        RecyclerView.ViewHolder(binding.root) {
        val iconImageView = binding.iconItem
        val name = binding.textItem1
        val age = binding.textItem2
    }

👉 RecyclerView 및 LayoutManager 설정 : Multi-view Type

// MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var binding:ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val dataList = mutableListOf(
            DogItems.MyTitle("1"),
            DogItems.MyItem(R.drawable.sample_3, "Duke", "1"),
            DogItems.MyItem(R.drawable.sample_6, "Luna", "1"),
            DogItems.MyItem(R.drawable.sample_8, "Merry", "1"),
            DogItems.MyItem(R.drawable.sample_13, "mung", "1"),
            DogItems.MyTitle("2"),
            DogItems.MyItem(R.drawable.sample_1, "Charlie", "2"),
            DogItems.MyItem(R.drawable.sample_4, "Max", "2"),
            DogItems.MyItem(R.drawable.sample_7, "Bob", "2"),
            DogItems.MyItem(R.drawable.sample_11, "Sara", "2"),
            DogItems.MyItem(R.drawable.sample_10, "ganga", "2"),
            DogItems.MyTitle("3"),
            DogItems.MyItem(R.drawable.sample_5, "Happy", "3"),
            DogItems.MyItem(R.drawable.sample_0, "Bella", "3"),
            DogItems.MyTitle("4"),
            DogItems.MyItem(R.drawable.sample_2, "Daisy", "4"),
            DogItems.MyItem(R.drawable.sample_9, "Jisoo", "4"),
            DogItems.MyItem(R.drawable.sample_12, "Ddong", "4")
        )

        val adapter = MyAdapter(dataList)
        binding.recyclerView.adapter = adapter
        binding.recyclerView.layoutManager = LinearLayoutManager(this)

				// StickyHeaderDecoration
        binding.recyclerView.addItemDecoration(StickyHeaderDecoration())
        binding.recyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayout.VERTICAL))

        adapter.itemClick = object : MyAdapter.ItemClick {
            override fun onClick(view: View, position: Int) {
                val name: String = (dataList[position] as DogItems.MyItem).aName
                Toast.makeText(this@MainActivity," $name 선택!", Toast.LENGTH_SHORT).show()
            }
        }
    }
}
```![](https://velog.velcdn.com/images/ouowinnie/post/4b9fe031-8792-4cf3-a700-25a7a3f3dbde/image.md)
profile
나도 개발 할래
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 9월 12일

잘 정리하셨군요! 다을님 TIL로 저도 코틀린 개념 차곡차곡 쌓아보겠씁니다~

답글 달기