ข้ามไปยังเนื้อหา

Slivers & Scrolling

list ที่ scroll ได้อาจมีหนึ่งหมื่น item แต่หน้าจอโชว์แค่ยี่สิบ การ build ทั้งหมื่นตัวคือหายนะ ไอเดียหลักของ scrolling ใน Flutter คือ lazy building: build เฉพาะ item ที่อยู่ใกล้พื้นที่มองเห็น แล้ว recycle ตอน scroll

นี่คือเหตุผลที่ constructor ที่คุณเลือกสำคัญมาก:

// ❌ build child ทั้งหมดตั้งแต่แรก — โอเคถ้ามีไม่กี่ตัว, หายนะถ้ามีเป็นพัน
ListView(children: [ for (final item in items) Tile(item) ])
// ✅ build child ตามที่ scroll เข้ามาในพื้นที่มองเห็น
ListView.builder(
itemCount: items.length,
itemBuilder: (context, i) => Tile(items[i]),
)

ListView.builder (และ GridView.builder) เรียก itemBuilder เฉพาะ item ที่ viewport ต้องการ สำหรับ list ที่อาจยาว ใช้แบบ builder เสมอ

ใต้ scrollable มี viewport — หน้าต่างที่คุณเห็น — และข้างในมี sliver sliver คือพื้นที่ scroll ได้ที่รู้วิธี layout ตัวเองแบบ lazy เทียบกับ viewport ListView เป็นตัวสะดวกที่ห่อ sliver ตัวเดียว; เมื่อคุณต้องการ scroll effect มากกว่าหนึ่งใน scroll view เดียว คุณจะประกอบ sliver เอง

flowchart TB
  scroll["CustomScrollView"] --> vp["Viewport
(visible window)"]
  vp --> s1["SliverAppBar
(collapses on scroll)"]
  vp --> s2["SliverList
(builds only visible tiles)"]
  vp --> offscreen["off-screen tiles:
not built yet"]
viewport build เฉพาะ sliver ที่อยู่ในสายตาแบบ lazy

CustomScrollView รับ list ของ sliver แล้ว scroll เป็นหนึ่งเดียว นี่คือวิธีสร้าง header ที่ collapse เหนือ list หรือวาง grid กับ list ใน scroll view เดียวกัน

CustomScrollView(
slivers: [
const SliverAppBar(
floating: true,
expandedHeight: 200, // header ที่ collapse ตอน scroll
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) => Tile(items[i]), // ยัง lazy อยู่
childCount: items.length,
),
),
],
)

mental model: widget ปกติกินพื้นที่กล่องคงที่; sliver กินพื้นที่ scroll ได้และเจรจากับ viewport ว่าจะมองเห็นตัวเองแค่ไหน SliverAppBar, SliverList, SliverGrid และ SliverToBoxAdapter (เอา widget ปกติหย่อนลงใน list ของ sliver) คือชิ้นส่วนที่คุณเอามาประกอบ

  • ซ้อน scrollable ทิศทางเดียวกัน (ListView ใน ListView) จะแย่ง scroll กันและ throw error เรื่อง unbounded constraint — ใช้ sliver หรือ shrinkWrap อย่างตั้งใจ
  • shrinkWrap: true ทำให้ list size ตาม content แทน viewport แต่ build ทุกอย่าง และเสีย laziness — ใช้กับ list เล็กจริง ๆ เท่านั้น
  • ไม่ใส่ key บน list ที่เรียงลำดับใหม่ได้ ทำให้ state ผิดตัวไปเกาะกับ item ผิดตัว (ดูบทเรียน Keys)
ทำไมต้องใช้ ListView.builder สำหรับ list ยาว?
sliver คืออะไร?
เมื่อไรควรใช้ CustomScrollView?
ข้อเสียของ shrinkWrap: true บน list ใหญ่คืออะไร?