RecyclerView를 구현해보자

순순·2024년 4월 3일

Android

목록 보기
2/12

LinearLayout이나 ConstraintLayout은 그래도 개념적으로 어려운 건 없었는데
RecyclerView는... 한번 정리할 필요를 느껴서 구현하면서 내용을 적어봤다.



1. RecyclerView 란?

Recycle이라는 단어에서 알 수 있듯 view를 재사용하는 레이아웃


(1) view를 재사용한다는게 무슨 뜻일까?

  • 우선 RecyclerView는 ListView의 단점을 보완한 친구라는걸 알아야 한다.
  • ListView의 단점?
    • 리스트 항목이 갱신될 때 마다 매번 아이템 뷰를 새로 구성해야 함
      ⇒ 대량의 데이터 집합을 표시할때 성능이 저하됨
    • 수직(Vertical) 방향으로만 나열 가능
    • 동적(Dynamically)으로 구성하기 쉽지 않음
      ⇒ 사용자의 선택에 따라 아이템 뷰의 형태 바꾸기 어려움

예를 들어, ListView로 게시글 목록 10,000개를 띄우려면 10,000개의 뷰를 만들어야 된다는 건데... 뷰 띄우는데 한참 걸릴 것이 분명하다.

반면 리사이클러뷰는 화면에 보일 만큼의 뷰를 만든다. ex) 10개
그리고 사용자가 화면을 스크롤하면 화면 위로 올라가서 더 이상 보이지 않게 되는 뷰들을 다시 갖다 쓴다.

따라서 view 를 재사용한다의 의미는 view를 새로 생성하지 않고, 기존에 사용했던 view의 데이터만 바꿔서 다시 사용한다는 뜻이라고 보면 된다.

중요한건 viewHolder를 통해 view를 관리 및 재사용하고 view 안의 데이터만 리셋한다는 것!

그렇다면 어떻게 그게 가능한건데? 라는 물음이 자연스럽게 떠오른다.
바로 adapter와 viewHolder가 있기에 가능한 것!

아래에서 그 두 가지에 대해 더 자세히 알아보도록 하자.



(2) RecyclerView의 특징

어댑터, 뷰홀더를 갖는다.


1) Adapter

  • Adapter는 데이터 리스트를 관리하여 포지션에 맞게 viewHolder의 view와 연결하여 표시하는 역할.

  • RecyclerView.Adapter는 추상 클래스이다. 따라서 메서드 오버라이딩이 강제된다.

  • 오버라이딩 해야하는 메서드는 3개다.

    onCreateViewHolder()

    getItemCount()

    onBindViewHolder()


2) ViewHolder

  • ViewHolders는 RecyclerView의 각 항목을 나타내는 View의 틀이라고 보면 된다.

  • 각 항목의 View를 보유하고 관리하는 역할을 한다.

  • 각 항목의 레이아웃 및 동작을 정의하는 데에 사용한다.


3) RecyclerView에 띄울 화면 = xml 파일

  • 어댑터나 뷰홀더가 화면은 아니기 때문에 리사이클러뷰 내부의 화면을 구성할 xml 파일을 별도로 생성해준다.



어떤 친구인지 대충 느낌이 왔다면 이제 구현해보자




2. RecyclerView 구현

내가 구현한 순서는 이렇다.

  1. 뷰 바인딩
  2. 리사이클러뷰에 띄울 항목 화면 구성 (row.xml)
  3. 리사이클러뷰 어댑터와 뷰홀더 구현
  4. 리사이클러뷰 어댑터 적용
  5. 메서드 호출



(1) ViewBinding

  • build.gradle.kts 파일에 buildFeatures 작성 후 상단에 'Syn' 뜨면 눌러서 적용
    buildFeatures {
        viewBinding = true
    }
  • MainActivity.kt에 뷰바인딩 적용
class MainActivity : AppCompatActivity() {
    lateinit var activityMainBinding: ActivityMainBinding

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

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)
    }


(2) 리사이클러뷰에 띄울 화면 구성 (row.xml)

  • 심플하게 리니얼레이아웃에 textView를 2개 배치 해주었다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="30dp">

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="제목" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="내용" />
</LinearLayout>

(3) 리사이클러뷰 어댑터와 뷰홀더 구현

리사이클러뷰 어댑터와 뷰홀더 구현 부분의 전체 코드는 이렇다.


    // 리사이클러뷰 어댑터 구현
    inner class Adapter: RecyclerView.Adapter<Adapter.ViewHolder>(){
        // 리사이클러뷰 뷰홀더 구현
      inner class ViewHolder(rowBinding: RowBinding):RecyclerView.ViewHolder(rowBinding.root){
          var rowBinding:RowBinding

          init {
              this.rowBinding = rowBinding

              // 레이아웃 구성
              this.rowBinding.root.layoutParams = ViewGroup.LayoutParams(
                  ViewGroup.LayoutParams.MATCH_PARENT,
                  ViewGroup.LayoutParams.WRAP_CONTENT
              )

          }
      }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            // row.xml 뷰바인딩
            val rowBinding = RowBinding.inflate(layoutInflater)
            // 뷰홀더 객체 생성
            val viewHolder = ViewHolder(rowBinding)
            return viewHolder
        }

        override fun getItemCount(): Int {
			// 생성할 항목의 수
            return 40
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            // 뷰홀더에 엮을 내용
            holder.rowBinding.textView.text = "게시글 ${position}"
            holder.rowBinding.textView2.text = "게시글 내용은 별거 없습니다"

        }
    }


조금씩 떼어서 볼까!


1) adapter 클래스 생성

inner class Adapter: RecyclerView.Adapter<Adapter.ViewHolder>()
  • 보다시피 Adapter는 내가 임의로 정해준 클래스명이고 상속을 받고 있다.
  • 상속받는 부모 클래스의 이름이 RecyclerView.Adapter 이다. (RecyclerView 라이브러리)
  • Adapter<> 안에 들어가는 건 ViewHolder 클래스명이다.
  • 따라서 adapter 클래스를 만들려면 ViewHolder 클래스를 먼저 만들어줘야 한다.

2) viewHolder 클래스 생성

  • 생성자의 매개변수로 RowBinding이 들어가 있다.
  • RowBinding은 아까 만들어주었던 row.xml 파일로부터 자동 생성된 바인딩 클래스이다.
    (뷰바인딩 사용시 xml 파일의 이름에 맞춰 Row+Binding으로 자동으로 생성됨)
inner class ViewHolder(rowBinding: RowBinding):RecyclerView.ViewHolder(rowBinding.root){

}

  • ViewHolder 클래스의 내부는 rowBidning 이라는 객체를 생성했고
  • init 블록을 통해 ViewHolder가 생성될 때 마다 레이아웃을 구성하도록 만들어주었다.
  • init 블록을 쓰면 일관된 방식으로 초기화하고 관련 코드가 중복되지 않도록 할 수 있다.
      inner class ViewHolder(rowBinding: RowBinding):RecyclerView.ViewHolder(rowBinding.root){
          var rowBinding:RowBinding

          init {
              this.rowBinding = rowBinding

              // 레이아웃 구성
              this.rowBinding.root.layoutParams = ViewGroup.LayoutParams(
                  ViewGroup.LayoutParams.MATCH_PARENT,
                  ViewGroup.LayoutParams.WRAP_CONTENT
              )

          }
      }

3) adapter 구현

  • 참고로 adapter는 추상클래스다. 덜 만들어진 클래스라 개발자가 원하는 목적에 맞춰 나머지를 구성해줘야 객체 생성이 가능하다는 뜻!! (따라서 메서드 오버라이딩이 강제된다)

  • onCreateViewHolder(), getItemCount(), onBindViewHolder()는 adapter를 구현하면 자동으로 따라오는 인터페이스이다. 어차피 구현 안해주면 이렇게 빨간 밑줄로 경고가 뜬다. 그냥 implement 해주면 된다.

  • 얘도 각각 무슨 역할을 하는지 하나씩 살펴 보자


onCreateViewHolder()

  • 처음 봤을 때 왜 매개변수가 2개나 있는지, 각각 무슨 역할을 하는지가 궁금해서 찾아봤다. (그거는 다른 포스팅에 정리할 예정)
  • 아무튼 이름대로 viewHolder를 create 해준다.
  • 여기서 row.xml 에 대한 inflate가 이루어지고 viewHolder 객체를 생성한다.
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            // row.xml 뷰바인딩
            val rowBinding = RowBinding.inflate(layoutInflater)
            // 뷰홀더 객체 생성
            val viewHolder = ViewHolder(rowBinding)
            // 리턴
            return viewHolder
        }

getItemCount()

  • 리사이클러뷰에 보일 항목의 개수 설정
  • 뷰홀더를 몇개 생성해야할지 정해주는 거라고 보면 된다.
  • 여기서는 그냥 임의로 40 넣어주었다.
        override fun getItemCount(): Int {
			// 생성할 항목의 수
            return 40
        }

onBindViewHolder

  • 항목의 데이터를 ViewHolder에 바인딩 시켜준다.


대충 흐름을 정리하자면 이런거다

getItemCounte() 뷰홀더 40개 생성해줘
-> onCreateViewHolder() ㅇㅇ뷰홀더 40개 생성할게
-> onBindViewHolder() 생성된 뷰홀더에 데이터 연결


여기까지 하면 구현은 끝났으니 이제 만든 거 적용만 해주면 된다.


(4) 리사이클러뷰 어댑터 적용

  • 어댑터를 구현했으니 이걸 쓰라고 설정해줘야 한다.
  • 여기서는 initView 라는 메서드를 따로 만들어서 적용해줬다.
  • 레이아웃 매니저를 까먹으면 화면 안나온다
  • deco는 항목 마다 구분해주는 구분선을 의미한다.

    // View를 초기화하는 메서드
    fun initView(){
        activityMainBinding.apply {
            // RecyclerView 설정
            recyclerViewResult.apply {
                // 어뎁터
                adapter = RecyclerViewAdapter()
                // 레이아웃 매니저
                layoutManager = LinearLayoutManager(this@MainActivity)
                // 데코레이션
                val deco = MaterialDividerItemDecoration(this@MainActivity, MaterialDividerItemDecoration.VERTICAL)
                addItemDecoration(deco)
            }
        }
    }


(5) 메서드 호출

  • 위에서 만들어준 initView를 onCreate 내부에서 호출해준다.
class MainActivity : AppCompatActivity() {
    lateinit var activityMainBinding: ActivityMainBinding

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

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

        initView()
    }
    




와아 끝!
맨 위에 있던 움짤처럼
리사이클러뷰가 잘 만들어진 걸 볼 수 있다.


음 어댑터를 구현하면서 코드상 의문이 드는 부분들이 몇몇 있었는데...


왜 뷰홀더 클래스를 제네릭 타입으로 하는지?
왜 inner class로 선언하는지?
매개변수에 들어간 parent는 뭔지? 등


메모장에 물음표밖에 없다 뭔 물음표 살인마도 아니고
아무튼 여기에 이거까지 다 적으면
여러 내용이 섞여 혼잡해질 것 같아서

그건 다른 게시글에 정리해서 작성해야겠다.

profile
플러터와 안드로이드를 공부합니다

0개의 댓글