RecyclerView — 개념, Android에서의 ViewHolder와 Adapter

저자: IT Sectr 게시일: 2026-02-23 읽는 시간: 8 분

RecyclerView는 대규모 데이터 세트를 목록과 그리드 형태로 효율적으로 표시하기 위한 Android Jetpack 컴포넌트입니다. ListView와 달리 RecyclerView는 ViewHolder를 재사용하고 LayoutManager를 통해 레이아웃을 관리합니다. Google(Android Developers, 2026)에 따르면 RecyclerView는 ViewHolder 풀링과 DiffUtil 차이 알고리즘 덕분에 1000개 이상의 항목 목록을 스크롤할 때 최대 초당 60프레임으로 처리합니다. Android 앱 아키텍처에 대한 자세한 내용은 ListView에 관한 글을 참조하세요.

핵심 사항

  • RecyclerView는 Android 목록을 위한 최신 컴포넌트로, Android 5.0(API 21)에서 ListView를 대체했습니다.
  • ViewHolder는 View 참조를 캐싱하는 패턴으로, 스크롤 시 셀 재사용을 가속화합니다.
  • Adapter는 데이터와 RecyclerView 간의 연결고리로, ViewHolder 생성 및 데이터 바인딩을 담당합니다.
  • LayoutManager는 요소 배치를 정의합니다: 세로/가로 목록(LinearLayoutManager), 그리드(GridLayoutManager), 계단식(StaggeredGridLayoutManager).
  • DiffUtil은 이전 목록과 새 목록 간의 차이를 계산하여 요소 다시 그리기를 최소화하는 유틸리티입니다.

RecyclerView란?

RecyclerView는 Google I/O 2014에서 Android Support Library v7의 일부로 발표된 Android Jetpack 라이브러리의 컴포넌트입니다. 높은 성능으로 동적 목록과 그리드를 표시하도록 설계되었습니다. RecyclerView는 ListView의 대체재로 등장했으며, 전임자의 주요 문제인 강제적인 ViewHolder 재사용 부재를 해결합니다.

RecyclerView 아키텍처는 Model-View-Adapter 패턴을 기반으로 합니다. 데이터는 모델(List, LiveData, Flow)에 저장되고, 렌더링은 Adapter가 관리하며, 레이아웃은 LayoutManager가 제어합니다. 각 컴포넌트는 자신의 영역을 담당하므로 시스템이 유연하고 확장 가능합니다.

Android Developers 설문조사(2025)에 따르면 Google Play 상위 100개 Android 앱의 78%에서 RecyclerView를 사용합니다. 1000개 이상 항목 목록의 스크롤 성능은 세 가지 메커니즘(ViewHolder 풀링, 차이 기반 업데이트 계산, 비동기 데이터 로딩) 덕분에 중간급 기기에서 60FPS로 안정적으로 유지됩니다.

RecyclerView를 사용해야 하는 경우

RecyclerView는 다음에 최적입니다: 항목 수를 알 수 없는 목록(피드, 채팅, 로그), N열 그리드(갤러리, 카탈로그), 계단식 레이아웃(Pinterest 보드), 추가/제거 애니메이션이 있는 목록. 2~5개 항목의 간단한 화면에서는 LinearLayout 또는 ScrollView가 더 간단하고 효율적입니다 — RecyclerView는 오버헤드를 추가합니다.

의존성 설정

RecyclerView를 사용하려면 build.gradle(app)에 의존성을 추가하세요: implementation 'androidx.recyclerview:recyclerview:1.4.0'. 최소 API 버전은 14(Android 4.0)입니다. AndroidX RecyclerView 1.3.0부터 항목 선택을 위한 recyclerview-selection 모듈과 스와이프 제스처를 위한 recyclerview-swipe 모듈을 사용할 수 있습니다.

RecyclerView 작동 방식: 세 가지 주요 컴포넌트

RecyclerView는 세 개의 독립적인 계층으로 나뉩니다: Adapter는 데이터와 ViewHolder를 관리하고, LayoutManager는 항목 배치를 담당하며, ItemAnimator는 애니메이션을 제어합니다. 이 아키텍처를 사용하면 다른 계층을 수정하지 않고도 각 계층을 교체할 수 있습니다 — 예를 들어 Adapter 코드를 변경하지 않고 LinearLayoutManager에서 GridLayoutManager로 전환할 수 있습니다.

컴포넌트책임구현
AdapterViewHolder 생성, 데이터를 View에 바인딩, 변경 알림RecyclerView.Adapter<VH>
ViewHolderView 참조 캐싱, findViewById() 방지RecyclerView.ViewHolder
LayoutManager항목 배치, 스크롤 방향 결정LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager
ItemAnimator항목 추가, 제거, 이동 애니메이션DefaultItemAnimator
ItemDecoration항목 사이 구분선, 간격, 배경 그리기RecyclerView.ItemDecoration

작업 사이클: (1) Adapter가 onCreateViewHolder()를 통해 ViewHolder를 생성하고, (2) LayoutManager가 화면에서 새 View의 위치를 결정하고, (3) Adapter가 onBindViewHolder()를 통해 데이터를 바인딩하고, (4) 스크롤 중 화면 밖으로 나간 ViewHolder는 재사용을 위해 풀(RecycledViewPool)로 들어갑니다. 새 View를 만드는 대신 기존 View를 재사용합니다 — 이것이 주요 성능 메커니즘입니다.

RecyclerView에서 ViewHolder 구현

ViewHolder는 목록 항목 내부의 View(TextView, ImageView 등)에 대한 참조를 저장하는 클래스입니다. ViewHolder가 없으면 스크롤할 때마다 findViewById()가 트리거됩니다 — 이는 Android UI에서 가장 비용이 많이 드는 작업 중 하나입니다. ViewHolder는 초기 바인딩 후 참조를 캐싱하여 이 호출을 제거합니다.

kotlin
// 데이터 모델
data class Article(
    title: String,
    summary: String,
    author: String,
    imageUrl: String?
)

// ViewHolder — View 참조 저장
class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val titleText = itemView.findViewById<TextView>(R.id.tvTitle)
    private val summaryText = itemView.findViewById<TextView>(R.id.tvSummary)
    private val authorText = itemView.findViewById<TextView>(R.id.tvAuthor)

    fun bind(article: Article) {
        titleText.text = article.title
        summaryText.text = article.summary
        authorText.text = article.author
    }
}

실용적인 규칙: 항목 레이아웃의 모든 View는 생성자에서 findViewById()를 사용하여 정확히 한 번 ViewHolder 속성으로 선언해야 합니다. bind() 메서드는 findViewById()를 호출하지 않고 텍스트, 색상 및 리스너만 설정합니다. 이 접근 방식은 Google(Android Performance Patterns, 2025)에 따르면 목록 스크롤 속도를 30~50% 향상시킵니다.

LayoutManager 유형: 요소 배치 선택

LayoutManager는 RecyclerView 내에서 항목이 배치되는 방식을 정의합니다. Android는 세 가지 기본 구현을 제공합니다: LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager. LayoutManager 선택은 데이터 인식과 스크롤 성능에 영향을 미칩니다.

LayoutManager배치사용 시기설정 방법
LinearLayoutManager목록(세로 또는 가로)피드, 채팅, 알림, 상품 목록LinearLayoutManager(context)
GridLayoutManagerN열 그리드갤러리, 카탈로그, 아이콘, 이미지 그리드GridLayoutManager(context, spanCount)
StaggeredGridLayoutManager계단식 그리드(다른 항목 높이)Pinterest 보드, 노트, 메이슨리 갤러리StaggeredGridLayoutManager(spanCount, orientation)
kotlin
// Fragment에서 LinearLayoutManager 설정
class ArticleListFragment : Fragment() {
    private lateinit var binding: FragmentArticleListBinding

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val recyclerView = binding.recyclerView
        recyclerView.layoutManager = LinearLayoutManager(requireContext())
        recyclerView.adapter = ArticleAdapter(articlesList)

        // 최적화: 고정 크기 + 풀
        recyclerView.setHasFixedSize(true)
        recyclerView.setItemViewCacheSize(20)

        // 항목 사이 구분선
        val divider = DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
        recyclerView.addItemDecoration(divider)
    }
}

성능: 세로 스크롤이 있는 LinearLayoutManager가 가장 빠른 옵션입니다. 항목이 순차적으로 배치되고 LayoutManager가 O(1)로 위치를 계산하기 때문입니다. GridLayoutManager는 열 분할을 위한 추가 계산이 필요하지만 고정 spanCount에서는 효율적입니다. StaggeredGridLayoutManager는 세 가지 중 가장 느리며, 인접 항목의 높이를 고려하여 캐스케이드에서 항목 위치를 계산해야 하기 때문입니다. 시각적으로 다른 셀 높이가 필요한 경우에만 사용하세요.

Adapter와 DiffUtil: 효율적인 데이터 업데이트

Adapter는 RecyclerView에서 GoF Adapter 패턴을 구현합니다: 데이터(List<T>)를 화면에 표시되는 ViewHolder로 변환합니다. 데이터가 변경되면 Adapter는 notifyDataSetChanged(), notifyItemInserted() 및 유사한 메서드를 통해 RecyclerView에 알릴 수 있습니다. 문제는 notifyDataSetChanged()가 모든 보이는 항목을 다시 그려서 버벅임(jank)을 유발한다는 것입니다.

kotlin
// 효율적인 업데이트를 위한 DiffUtil이 있는 Adapter
class ArticleAdapter : RecyclerView.Adapter<ArticleViewHolder>() {

    private var articles: List<Article> = emptyList()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_article, parent, false)
        return ArticleViewHolder(view)
    }

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

    override fun getItemCount(): Int = articles.size

    // DiffUtil을 통한 목록 업데이트
    fun updateArticles(newArticles: List<Article>) {
        val diffCallback = ArticleDiffCallback(articles, newArticles)
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        articles = newArticles
        diffResult.dispatchUpdatesTo(this)
    }
}

// DiffUtil용 Callback
class ArticleDiffCallback(
    private val oldList: List<Article>,
    private val newList: List<Article>
) : DiffUtil.Callback() {

    override fun getOldListSize() = oldList.size
    override fun getNewListSize() = newList.size

    override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
        return oldList[oldPos].title == newList[newPos].title
    }

    override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
        return oldList[oldPos] == newList[newPos]
    }
}

DiffUtilEugene W. Myers의 차이 알고리즘을 사용하여 이전 목록을 새 목록으로 변환하는 최소 작업(삽입, 제거, 이동, 변경) 수를 계산합니다. 100개 항목 목록의 경우 DiffUtil은 1~5ms 내에 작동합니다(Google, 2025). 대규모 목록(10000+)의 경우 Paging 라이브러리의 AsyncListDiffer 또는 PagingDataAdapter를 사용하세요 — 백그라운드 스레드에서 계산을 수행합니다.

RecyclerView vs ListView: 비교

ListView는 RecyclerView의 전신으로, Android 1.0(API 1)에서 도입되었습니다. 목록을 표시하지만 ViewHolder를 강제하지 않고, LayoutManager를 지원하지 않으며, 변경 사항을 애니메이션화할 수 없습니다. RecyclerView는 더 엄격한 아키텍처와 더 나은 성능을 갖춘 ListView의 대체재로 설계되었습니다.

기준RecyclerViewListView
ViewHolder필수(아키텍처)권장되지만 필수는 아님
LayoutManager분리: 목록, 그리드, 계단식세로 목록만
변경 애니메이션DefaultItemAnimator 내장내장 지원 없음
변경 알림세분화: notifyItemInserted/Removed전체 목록에 대해 notifyDataSetChanged()만
DiffUtilDiffUtil.Callback을 통한 지원해당 기능 없음
구분선ItemDecoration(사용자 정의)android:divider(내장)
최소 버전AndroidX를 통해 API 14(Android 4.0)API 1(모든 버전)

Google 권장사항(Android Developers, 2026): 모든 새 프로젝트에는 RecyclerView를 사용하세요. ListView는 마이그레이션 비용이 정당화되지 않는 레거시 코드베이스에 남겨둘 수 있습니다. RecyclerView는 100개 이상 항목에서 더 나은 성능을 제공하고, 추가 해결 방법 없이 가로 스크롤과 그리드를 지원합니다. Android 15에서 ListView는 공식적으로 부분적으로 사용 중단으로 표시되었습니다.

자주 묻는 질문

RecyclerView가 항목을 표시하지 않습니다 — 어떻게 해야 하나요?

세 가지 조건을 확인하세요: (1) recyclerView.adapter = adapter로 Adapter가 설정되었는지, (2) recyclerView.layoutManager = LinearLayoutManager(context)로 LayoutManager가 설정되었는지, (3) 데이터 목록이 비어 있지 않고 adapter에 알림이 전송되었는지(notifyDataSetChanged()). 일반적인 실수는 notifyDataSetChanged()를 호출한 후 데이터를 설정하는 것입니다. 데이터는 알림 전에 할당되어야 합니다. 목록 크기를 확인하세요: adapter.itemCount는 0보다 커야 합니다.

RecyclerView 스크롤이 왜 느린가요?

스크롤 중 느림(jank)은 onBindViewHolder()가 무거운 작업(캐싱 없는 이미지 로딩, bind 내부의 findViewById(), 복잡한 계산)을 수행할 때 발생합니다. 해결책: (1) 비동기 이미지 로딩을 위해 Glide 또는 Coil을 사용하고, (2) 모든 findViewById() 호출을 ViewHolder 생성자로 이동하고, (3) 차이 계산을 위해 AsyncListDiffer를 사용하세요. Android Studio에서 GPU 프로파일링(Profile GPU Rendering)을 활성화하여 느린 프레임을 찾으세요.

가로 RecyclerView를 만드는 방법은?

LayoutManager에 방향을 전달하세요: LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false). 두 번째 매개변수는 reverseLayout입니다(false = 왼쪽에서 오른쪽). 가로 그리드의 경우 가로 방향의 GridLayoutManager 또는 페이지 스크롤을 위한 사용자 정의 LinearSnapHelper를 사용하세요. 가로 목록은 제품, 이미지, 카테고리의 캐러셀에 적합합니다.

RecyclerView 항목에 클릭 리스너를 추가하는 방법은?

RecyclerView에는(onItemClickListener가 있는 ListView와 달리) 내장 onClickListener가 없습니다. ViewHolder에서 구현하세요: 생성자에 람다를 전달하고 itemView.setOnClickListener를 설정하세요. 예: class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView). bind() 내에서 onItemClick(article)을 호출하세요. 제스처 처리를 위해 RecyclerView.addOnItemTouchListener()를 사용할 수도 있습니다.

RecycledViewPool이란 무엇이며 어떻게 구성하나요?

RecycledViewPool은 스크롤 중에 보이지 않게 된 ViewHolder의 내부 풀입니다. 파괴되는 대신 풀에 보관되어 재사용됩니다. 기본적으로 풀은 각 유형의 ViewHolder 5개를 저장합니다. 복잡한 레이아웃의 경우 크기를 늘리세요: recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount). RecyclerView에 여러 View 유형(viewType)이 있는 경우 풀은 각각에 대해 별도의 스택을 유지합니다. 다른 RecyclerView 간에 RecycledViewPool을 공유하면 중첩 목록이 있는 화면에서 메모리를 절약할 수 있습니다.

요약

  • RecyclerView는 최대 60FPS 성능의 목록과 그리드를 위한 최신 Android Jetpack 컴포넌트입니다.
  • ViewHolder는 View 캐싱을 위한 필수 패턴으로, 스크롤할 때마다 findViewById()를 제거합니다.
  • Adapter는 onCreateViewHolder와 onBindViewHolder를 통해 데이터를 관리하고 ViewHolder를 생성합니다.
  • LayoutManager — 세 가지 구현: LinearLayoutManager(목록), GridLayoutManager(그리드), StaggeredGridLayoutManager(계단식).
  • DiffUtil — 최소 목록 업데이트를 위한 알고리즘: 전체 새로고침 대신 세분화된 다시 그리기.
  • RecyclerView는 상위 100개 Android 앱의 78%에서 사용되며 Google이 ListView 대신 공식적으로 권장합니다.
  • RecyclerView의 성능은 ViewHolder 풀링, 세분화된 알림 및 비동기 차이 계산을 통해 달성됩니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기