RecyclerView เป็นคอมโพเนนต์ของ Android Jetpack สำหรับแสดงชุดข้อมูลขนาดใหญ่ในรูปแบบรายการและกริดอย่างมีประสิทธิภาพ ต่างจาก ListView ตรงที่ RecyclerView นำ ViewHolder กลับมาใช้ใหม่และจัดการเลย์เอาต์ผ่าน LayoutManager ตามข้อมูลของ Google (Android Developers, 2026) RecyclerView ประมวลผลได้สูงสุด 60 เฟรมต่อวินาที เมื่อเลื่อนรายการที่มี 1000+ รายการ ต้องขอบคุณการรวม ViewHolder และอัลกอริธึมความแตกต่าง DiffUtil เรียนรู้เพิ่มเติมเกี่ยวกับสถาปัตยกรรมแอป Android ได้ใน บทความของเราเกี่ยวกับ ListView
ประเด็นสำคัญ
RecyclerView เป็นคอมโพเนนต์ของไลบรารี Android Jetpack ซึ่งเปิดตัวใน Google I/O 2014 โดยเป็นส่วนหนึ่งของ Android Support Library v7 มันถูกออกแบบมาเพื่อแสดงรายการและกริดแบบไดนามิกด้วยประสิทธิภาพสูง RecyclerView เกิดขึ้นมาแทนที่ ListView และแก้ปัญหาหลักของรุ่นก่อน — การขาดการบังคับใช้ ViewHolder ซ้ำ
สถาปัตยกรรมของ RecyclerView สร้างขึ้นบนรูปแบบ Model-View-Adapter: ข้อมูลถูกเก็บในโมเดล (List, LiveData, Flow), การเรนเดอร์ถูกจัดการโดย Adapter และเลย์เอาต์ถูกควบคุมโดย LayoutManager แต่ละคอมโพเนนต์รับผิดชอบพื้นที่ของตนเอง ทำให้ระบบมีความยืดหยุ่นและขยายได้
จากการสำรวจของ Android Developers (2025) RecyclerView ถูกใช้ใน 78% ของแอป Android ใน 100 อันดับแรกของ Google Play ประสิทธิภาพการเลื่อนสำหรับรายการ 1000+ รายการยังคงคงที่ที่ 60 FPS บนอุปกรณ์ระดับกลางด้วยสามกลไก: การรวม ViewHolder, การคำนวณอัปเดตตามความแตกต่าง และการโหลดข้อมูลแบบอะซิงโครนัส
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 แบ่งออกเป็นสามชั้นอิสระ: Adapter จัดการข้อมูลและ ViewHolder, LayoutManager จัดการตำแหน่งของรายการ และ ItemAnimator ควบคุมแอนิเมชัน สถาปัตยกรรมนี้ช่วยให้สามารถเปลี่ยนชั้นใดก็ได้โดยไม่ต้องแก้ไขชั้นอื่น — ตัวอย่างเช่น เปลี่ยนจาก LinearLayoutManager เป็น GridLayoutManager โดยไม่ต้องเปลี่ยนโค้ดของ Adapter
| คอมโพเนนต์ | ความรับผิดชอบ | การนำไปใช้ |
|---|---|---|
| Adapter | สร้าง ViewHolder, ผูกข้อมูลกับ View, แจ้งเตือนการเปลี่ยนแปลง | RecyclerView.Adapter<VH> |
| ViewHolder | แคชการอ้างอิง View, หลีกเลี่ยง findViewById() | RecyclerView.ViewHolder |
| LayoutManager | กำหนดตำแหน่งรายการ, กำหนดทิศทางการเลื่อน | LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager |
| ItemAnimator | แอนิเมชันสำหรับการเพิ่ม, ลบ, ย้ายรายการ | DefaultItemAnimator |
| ItemDecoration | วาดตัวแบ่ง, ระยะห่าง, พื้นหลังระหว่างรายการ | RecyclerView.ItemDecoration |
วงจรการทำงาน: (1) Adapter สร้าง ViewHolder ผ่าน onCreateViewHolder(), (2) LayoutManager กำหนดตำแหน่งของ View ใหม่บนหน้าจอ, (3) Adapter ผูกข้อมูลผ่าน onBindViewHolder(), (4) เมื่อเลื่อน ViewHolder ที่ออกจากหน้าจอจะเข้าสู่พูล (RecycledViewPool) เพื่อนำกลับมาใช้ใหม่ แทนที่จะสร้าง View ใหม่ จะนำ View ที่มีอยู่กลับมาใช้ใหม่ — นี่คือกลไกประสิทธิภาพหลัก
ViewHolder เป็นคลาสที่เก็บการอ้างอิงไปยัง View (TextView, ImageView ฯลฯ) ภายในรายการของลิสต์ หากไม่มี ViewHolder ทุกครั้งที่เลื่อนจะเรียก findViewById() — หนึ่งในการดำเนินการที่แพงที่สุดของ UI Android ViewHolder ขจัดการเรียกนี้โดยแคชการอ้างอิงหลังจากการผูกครั้งแรก
// โมเดลข้อมูล
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 ในเลย์เอาต์ของรายการควรประกาศเป็นคุณสมบัติของ ViewHolder ด้วย findViewById() เพียงครั้งเดียว — ในคอนสตรัคเตอร์ เมธอด bind() ไม่เรียก findViewById() แต่จะตั้งค่าข้อความ สี และตัวฟังเท่านั้น วิธีการนี้เร่งการเลื่อนรายการได้ 30–50% ตามข้อมูลของ Google (Android Performance Patterns, 2025)
LayoutManager กำหนดว่ารายการถูกวางตำแหน่งภายใน RecyclerView อย่างไร Android มีการใช้งานเริ่มต้นสามแบบ: LinearLayoutManager, GridLayoutManager และ StaggeredGridLayoutManager การเลือก LayoutManager ส่งผลต่อการรับรู้ข้อมูลและประสิทธิภาพการเลื่อน
| LayoutManager | การจัดเรียง | เมื่อใดควรใช้ | วิธีการตั้งค่า |
|---|---|---|---|
| LinearLayoutManager | รายการ (แนวตั้งหรือแนวนอน) | ฟีด, แชท, การแจ้งเตือน, รายการสินค้า | LinearLayoutManager(context) |
| GridLayoutManager | กริด N คอลัมน์ | แกลเลอรี, แคตตาล็อก, ไอคอน, กริดรูปภาพ | GridLayoutManager(context, spanCount) |
| StaggeredGridLayoutManager | กริดซิกแซก (ความสูงรายการต่างกัน) | บอร์ด Pinterest, โน้ต, แกลเลอรีแบบ masonry | StaggeredGridLayoutManager(spanCount, orientation) |
// การตั้งค่า LinearLayoutManager ใน Fragment
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 ใน RecyclerView นำรูปแบบ Adapter ของ GoF ไปใช้: มันแปลงข้อมูล (List<T>) เป็น ViewHolder ที่แสดงบนหน้าจอ เมื่อข้อมูลเปลี่ยน Adapter สามารถแจ้ง RecyclerView ผ่าน notifyDataSetChanged(), notifyItemInserted() และเมธอดที่คล้ายกัน ปัญหาคือ notifyDataSetChanged() วาดรายการที่มองเห็นทั้งหมดใหม่ ทำให้เกิดการกระตุก (jank)
// Adapter ที่มี DiffUtil สำหรับการอัปเดตที่มีประสิทธิภาพ
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)
}
}
// Callback สำหรับ DiffUtil
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]
}
}
DiffUtil ใช้อัลกอริธึม ความแตกต่างของ Eugene W. Myers ซึ่งคำนวณจำนวนการดำเนินการขั้นต่ำ (แทรก, ลบ, ย้าย, เปลี่ยน) เพื่อแปลงรายการเก่าเป็นรายการใหม่ สำหรับรายการ 100 รายการ DiffUtil ทำงานใน 1–5 มิลลิวินาที (Google, 2025) สำหรับรายการขนาดใหญ่ (10000+) ให้ใช้ AsyncListDiffer หรือ PagingDataAdapter จากไลบรารี Paging — พวกเขาคำนวณในเธรดพื้นหลัง
ListView เป็นรุ่นก่อนของ RecyclerView เปิดตัวใน Android 1.0 (API 1) มันแสดงรายการได้เช่นกันแต่ไม่บังคับใช้ ViewHolder ไม่รองรับ LayoutManager และไม่สามารถเคลื่อนไหวการเปลี่ยนแปลงได้ RecyclerView ถูกออกแบบมาแทนที่ ListView ด้วยสถาปัตยกรรมที่เข้มงวดกว่าและประสิทธิภาพที่ดีกว่า
| เกณฑ์ | RecyclerView | ListView |
|---|---|---|
| ViewHolder | บังคับ (สถาปัตยกรรม) | แนะนำแต่ไม่บังคับ |
| LayoutManager | แยก: รายการ, กริด, ซิกแซก | เฉพาะรายการแนวตั้ง |
| แอนิเมชันการเปลี่ยนแปลง | DefaultItemAnimator ในตัว | ไม่มีการรองรับในตัว |
| การแจ้งเตือนการเปลี่ยนแปลง | ละเอียด: notifyItemInserted/Removed | เฉพาะ notifyDataSetChanged() สำหรับทั้งรายการ |
| DiffUtil | รองรับผ่าน DiffUtil.Callback | ไม่มีสิ่งที่เทียบเท่า |
| ตัวแบ่ง | ItemDecoration (กำหนดเอง) | android:divider (ในตัว) |
| เวอร์ชันขั้นต่ำ | API 14 (Android 4.0) ผ่าน AndroidX | API 1 (ทุกเวอร์ชัน) |
คำแนะนำของ Google (Android Developers, 2026): ใช้ RecyclerView สำหรับโปรเจกต์ใหม่ทั้งหมด ListView สามารถคงอยู่ในโค้ดเบสเก่าที่การย้ายถิ่นไม่คุ้มค่า RecyclerView ให้ประสิทธิภาพที่ดีกว่าบน 100+ รายการ รองรับการเลื่อนแนวนอนและกริดโดยไม่ต้องมีวิธีแก้ไขเพิ่มเติม ใน Android 15 ListView ถูกทำเครื่องหมายอย่างเป็นทางการว่าล้าสมัยบางส่วน
คำถามที่พบบ่อย
ตรวจสอบสามเงื่อนไข: (1) Adapter ถูกตั้งค่าผ่าน recyclerView.adapter = adapter, (2) LayoutManager ถูกตั้งค่าผ่าน recyclerView.layoutManager = LinearLayoutManager(context), (3) รายการข้อมูลไม่ว่างและ adapter ได้รับการแจ้งเตือนแล้ว (notifyDataSetChanged()) ข้อผิดพลาดทั่วไปคือการตั้งค่าข้อมูลหลังจากเรียก notifyDataSetChanged() ข้อมูลควรถูกกำหนดก่อนการแจ้งเตือน ตรวจสอบขนาดรายการ: adapter.itemCount ต้องมากกว่า 0
ความช้า ขณะเลื่อน (jank) เกิดขึ้นเมื่อ onBindViewHolder() ดำเนินการหนัก: โหลดรูปภาพโดยไม่แคช, findViewById() ภายใน bind, การคำนวณซับซ้อน วิธีแก้: (1) ใช้ Glide หรือ Coil สำหรับโหลดรูปภาพแบบอะซิงโครนัส, (2) ย้ายการเรียก findViewById() ทั้งหมดไปยังคอนสตรัคเตอร์ของ ViewHolder, (3) สำหรับการคำนวณความแตกต่าง ให้ใช้ AsyncListDiffer เปิดการสร้างโปรไฟล์ GPU ใน Android Studio (Profile GPU Rendering) เพื่อค้นหาเฟรมที่ช้า
ส่งการวางแนวไปยัง LayoutManager: LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) พารามิเตอร์ที่สองคือ reverseLayout (false = ซ้ายไปขวา) สำหรับกริดแนวนอน ให้ใช้ GridLayoutManager ที่มีการวางแนวแนวนอนหรือ LinearSnapHelper ที่กำหนดเองสำหรับการเลื่อนแบบหน้า รายการแนวนอนเหมาะสำหรับม้าหมุนสินค้า รูปภาพ หมวดหมู่
RecyclerView ไม่มี onClickListener ในตัว (ต่างจาก ListView ที่มี onItemClickListener) นำไปใช้ใน ViewHolder: ส่งแลมบ์ดาไปยังคอนสตรัคเตอร์และตั้งค่า itemView.setOnClickListener ตัวอย่าง: class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView) เรียก onItemClick(article) ภายใน bind() อีกทางเลือกคือใช้ RecyclerView.addOnItemTouchListener() สำหรับการจัดการท่าทาง
RecycledViewPool คือพูลภายในของ ViewHolder ที่มองไม่เห็นเมื่อเลื่อน แทนที่จะถูกทำลาย พวกมันถูกเก็บในพูลและนำกลับมาใช้ใหม่ โดยค่าเริ่มต้น พูลเก็บ 5 ViewHolder ต่อประเภท เพิ่มขนาดสำหรับเลย์เอาต์ที่ซับซ้อน: recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount) ถ้า RecyclerView มีหลายประเภท View (viewType) พูลจะเก็บสแต็กแยกสำหรับแต่ละประเภท การแชร์ RecycledViewPool ระหว่าง RecyclerView ต่างๆ ช่วยประหยัดหน่วยความจำในหน้าจอที่มีรายการซ้อนกัน
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ