RecyclerView로 동적 목록 만들기

kosdjs·2025년 9월 22일

Android

목록 보기
23/29
  • RecyclerView를 사용하면 대량의 데이터 셋을 효율적으로 표시할 수 있음, 데이터를 제공하고 아이템이 어떻게 표시되는지 정의하면 RecyclerView 라이브러리가 요소가 필요할 때 동적으로 생성해줌

  • 이름에서 알 수 있듯, RecyclerView 는 개별 요소를 재활용함, 아이템이 화면 밖으로 스크롤될때 RecyclerView 는 그 뷰를 파괴하지 않음, 대신 RecyclerView 는 화면으로 스크롤되는 새로운 아이템을 위해 뷰를 재사용함, 이런 점에서 RecyclerView 는 앱의 반응성과 성능을 개선하고 전원 소비를 줄여줌

노트: RecyclerView 는 클래스와 동시에 이를 포함하는 라이브러리의 이름입니다. 이 페이지에서 RecyclerView를 코드 블록에 표시하면 라이브러리의 클래스를 의미합니다.

주요 클래스

  • RecyclerView 는 데이터에 대응되는 뷰를 포함하는 ViewGroup 임, 이는 RecyclerView 가 결국 뷰라는 뜻으로 다른 UI 요소를 레이아웃에 추가하는 방법 그대로 추가할 수 있다는 뜻임

  • 리스트의 각각의 요소는 뷰 홀더 객체임, 뷰 홀더가 생성될 때는 데이터를 가지고 있지 않음, 뷰 홀더가 생성되고 나면 RecyclerView 에서 데이터와 연결해줌, 뷰 홀더는 RecyclerView.ViewHolder 를 상속받아 정의할 수 있음

  • RecyclerView 는 어댑터의 메소드를 호출해 뷰를 요청하고 뷰를 데이터와 연결함, 어댑터는 RecyclerView.Adapter 를 상속받아 정의할 수 있음

  • 레이아웃 매니저는 리스트의 개별 요소를 정렬함, 라이브러리에서 제공되는 레이아웃 매니저를 사용하거나 직접 정의해서 사용할 수 있음, 레이아웃 매니저는 라이브러리의 LayoutManager 추상 클래스를 기반으로 만들어짐

RecyclerView 구현 단계

  1. 리스트나 그리드를 어떻게 표시할지 결정함, 일반적으로 RecyclerView 라이브러리의 표준 레이아웃 매니저를 사용함

  2. 리스트의 각 요소가 어떻게 표시되고 동작할지 설계함, 설계에 따라 ViewHolder 클래스를 상속해 만듬, 이렇게 만든 ViewHolder 는 리스트 아이템을 위한 모든 기능을 제공해야 함, 뷰 홀더는 View를 감싸는 클래스이고 그 뷰는 RecyclerView 에 의해 관리됨

  3. ViewHolder 와 데이터를 연결하는 Adapter 를 정의함

레이아웃 설계

  • RecyclerView 의 아이템은 LayoutManager 클래스에 의해 정렬됨, RecyclerView 라이브러리는 대부분의 레이아웃을 처리하는 세 개의 레이아웃 매니저를 제공함
    • LinearLayoutManager 는 1차원 리스트로 아이템을 정렬함
    • GridLayoutManager 는 2차원 그리드로 아이템을 정렬함
      • 그리드가 수직으로 정렬되었다면, GridLayoutManager 는 각 행의 모든 요소를 같은 너비와 높이로 만듬, 행마다 높이는 다르게 가질 수 있음
      • 그리드가 수평으로 정렬되었다면, GridLayoutManager 는 각 열의 모든 요소를 같은 너비와 높이로 만듬, 열마다 너비는 다르게 가질 수 있음
    • StaggeredGridLayoutManagerGridLayoutManager 와 비슷하지만 수직 그리드의 경우 행의 아이템에 대해 모두 같은 높이, 수평 그리드의 경우 열의 아이템에 대해 모두 같은 너비를 요구하지 않음, 그 결과로 같은 행이나 열에 있는 아이템들이 어긋나도록 배치될 수 있음

뷰 홀더와 어댑터 구현

  • 레이아웃을 결정하고 나면 AdapterViewHolder 를 구현해야 함, 이 두 클래스는 데이터를 어떻게 표시할 것인지 정할 때 같이 사용됨, ViewHolder 는 리스트의 개별 아이템을 위한 레이아웃을 포함하는 View 의 래퍼 클래스임, Adapter 는 필요한만큼 ViewHolder 객체를 만들고 이 뷰들을 위한 데이터를 설정함, 뷰에 데이터를 연결하는 작업을 binding 이라고 함

  • 어댑터를 정의할 때 다음 세가지 주요 메소드를 오버라이드해야 함

    • onCreateViewHolder() : RecyclerView 가 새 ViewHolder 를 생성해야 할 때 호출함, 이 메소드는 ViewHolder 와 연결된 View 를 생성하고 초기화함, 하지만 뷰의 내용을 채우지는 않음, 즉, ViewHolder 는 아직 특정 데이터와 연결되지 않은 상태임
    • onBindViewHolder() : RecyclerViewViewHolder 와 데이터를 연결할 때 이 메소드를 호출함, 이 메소드는 적절한 데이터를 찾고 그 데이터를 사용해 뷰 홀더의 레이아웃을 채움, 예시로 RecyclerView 가 이름의 리스트를 표시한다면 이 메소드는 리스트에서 적절한 이름을 찾고 뷰 홀더의 TextView 위젯을 채움
    • getItemCount() : RecyclerView 가 데이터셋의 크기를 얻을 때 사용하는 메소드, 예시로 주소책 앱이라면 이 메소드는 주소의 총 갯수가 반환될 수 있음, 이 메소드는 더이상 표시할 아이템이 없는지 결정할 때 사용됨
  • 다음은 간단한 어댑터와 데이터의 리스트를 표시하는 중첩된 ViewHolder 의 일반적인 예제임, 이 경우에 RecyclerView는 간단한 텍스트의 리스트를 표시함, 어댑터에 ViewHolder 요소를 위한 문자열의 배열이 전달됨

class CustomAdapter(private val dataSet: Array<String>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    /**
     * 사용하는 뷰 유형의 참조를 제공합니다.
     * (사용자 정의 ViewHolder)
     */
    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView

        init {
            // ViewHolder의 뷰를 위한 클릭 리스너 정의
            textView = view.findViewById(R.id.textView)
        }
    }

    // 새로운 뷰 생성 (레이아웃 관리자에 의해 호출됨)
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        // 리스트 아이템의 UI를 정의하는 새로운 뷰를 생성
        val view = LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.text_row_item, viewGroup, false)

        return ViewHolder(view)
    }

    // 뷰의 내용 교체 (레이아웃 관리자에 의해 호출됨)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {

        // 데이터 셋에서 해당 위치에 있는 요소를 가져와
        // 뷰의 내용을 해당 요소로 교체함
        viewHolder.textView.text = dataSet[position]
    }

    // 데이터 셋의 크기를 반환 (레이아웃 관리자에 의해 호출됨)
    override fun getItemCount() = dataSet.size

}
  • 각 뷰를 위한 레이아웃은 일반적으로 XML 레이아웃 파일로 정의됨, 이 경우 앱은 다음과 같은 text_row_item.xml 파일을 가지고 있음
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/list_item_height"
    android:layout_marginLeft="@dimen/margin_medium"
    android:layout_marginRight="@dimen/margin_medium"
    android:gravity="center_vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/element_text"/>
</FrameLayout>
  • 다음 코드는 RecyclerView 를 어떻게 사용하는지 보여줌
class MainActivity : AppCompatActivity() {

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

        val dataset = arrayOf("January", "February", "March")
        val customAdapter = CustomAdapter(dataset)

        val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = customAdapter

    }

}

엣지 투 엣지 디스플레이 활성화

  • 다음 단계를 따라해 RecyclerView 를 위한 엣지 투 엣지 디스플레이를 활성화함

    • enableEdgeToEdge() 를 호출해 하위 호환 가능한 엣지 투 엣지 디스플레이를 설정함

    • 만약 리스트 아이템이 시스템 바를 덮는다면 RecyclerView 에 inset을 적용함, android:fitsSystemWindowstrue 로 설정하거나 ViewCompat.setOnApplyWindowInsetsListener 를 사용해 적용할 수 있음

    • RecyclerViewandroid:clipToPaddingfalse 로 설정해 리스트 아이템이 시스템 바 밑에 그려질 수 있도록 허용해야 함

RecyclerView 에 엣지 투 엣지 디스플레이가 비활성화(왼쪽)된 것과 활성화(오른쪽)된 것을 보여주는 영상

inset을 설정하는 코드 예시

ViewCompat.setOnApplyWindowInsetsListener(
  findViewById(R.id.my_recycler_view)
  ) { v, insets ->
      val innerPadding = insets.getInsets(
          WindowInsetsCompat.Type.systemBars()
                  or WindowInsetsCompat.Type.displayCutout()
          // EditText를 사용한다면 
          // "or WindowInsetsCompat.Type.ime()"
          // 도 추가해 IME를 열었을 때에도 포커스를 유지함
      )
      v.setPadding(
          innerPadding.left,
          innerPadding.top,
          innerPadding.right,
          innerPadding.bottom)
      insets
  }

RecyclerView를 적용하는 XML 예시

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/my_recycler_view"
    android:clipToPadding="false"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

원문: https://developer.android.com/develop/ui/views/layout/recyclerview

0개의 댓글