RenderObject Tree คือระดับที่สามของลำดับชั้นใน Flutter ที่รับผิดชอบการเรนเดอร์อินเทอร์เฟซจริงบนหน้าจอ แต่ละโหนดของทรีนี้ทำการคำนวณขนาด (layout) และการวาด (painting) แปลงค่าคอนฟิกูเรชันของวิดเจ็ตเป็นพิกเซล ต่างจาก Widget Tree ตรงที่ RenderObject Tree ถูกสร้างครั้งเดียวและอัปเดตเฉพาะเมื่อเรขาคณิตหรือลักษณะภายนอกเปลี่ยนแปลง ตาม Flutter API Reference, 2025 ประสิทธิภาพของ RenderObject Tree กำหนดความลื่นไหลของแอนิเมชันและความเร็วในการตอบสนองของแอปพลิเคชันโดยตรง
ประเด็นสำคัญ
RenderObject Tree — โครงสร้างลำดับชั้นของออบเจ็กต์การเรนเดอร์ที่เป็นตัวแทนจริงของอินเทอร์เฟซในหน่วยความจำ แต่ละ RenderObject รู้ขนาดของตัวเอง ตำแหน่งบนหน้าจอ และสามารถวาดตัวเองได้ ต่างจากวิดเจ็ตที่มีน้ำหนักเบาและสามารถถูกสร้างใหม่ได้หลายร้อยครั้งต่อวินาที RenderObject เป็นออบเจ็กต์หนักที่มีการเข้าถึงไปป์ไลน์กราฟิกโดยตรง
เส้นทางจากวิดเจ็ตไปยังพิกเซลผ่านสามขั้นตอน: Widget Tree อธิบายสิ่งที่ควรปรากฏบนหน้าจอ; Element Tree จัดการวงจรชีวิต; RenderObject Tree ดำเนินงานจริง แต่ละ RenderObjectWidget (เช่น Padding, Transform, CustomPaint) สร้าง RenderObject ที่ถูกเพิ่มเข้าไปในทรีการเรนเดอร์ RenderBox เป็นประเภท RenderObject ที่พบมากที่สุด ใช้สำหรับ 99% ของวิดเจ็ตมาตรฐาน
RenderObject — คลาสนามธรรมที่กำหนดอินเทอร์เฟซสำหรับ layout และ paint ประกอบด้วยการอ้างอิงไปยัง RenderObject หลักและออบเจ็กต์ลูก รวมถึงเมธอดนามธรรม: performLayout, paint และ hitTest แต่ละ RenderObject ที่เป็นรูปธรรมนำเมธอดเหล่านี้ไปใช้ตามพฤติกรรมของตนเอง: RenderFlex กระจายพื้นที่ระหว่างเอลิเมนต์ลูก, RenderImage แสดงภาพ, RenderParagraph แสดงข้อความ
abstract class RenderObject {
RenderObject? parent;
Constraints constraints;
ParentData? parentData;
bool _needsLayout = true;
bool _needsPaint = true;
void performLayout();
void paint(PaintingContext context, Offset offset);
}
ในโครงสร้างอย่างง่ายนี้ RenderObject มีแฟล็ก _needsLayout และ _needsPaint ที่ทำเครื่องหมายออบเจ็กต์ว่าต้องการอัปเดต เมื่อ Widget Tree เปลี่ยนแปลง Element Tree จะทำเครื่องหมาย RenderObject ที่เกี่ยวข้องว่า "สกปรก" สำหรับ layout หรือ paint และในเฟรมถัดไป Flutter จะดำเนินการเฉพาะที่จำเป็นเท่านั้น
แต่ละ RenderObject ดำเนินสองเฟสหลัก: layout (การกำหนดขนาดและตำแหน่ง) และ paint (การวาด) เฟสเหล่านี้ดำเนินการตามลำดับที่เข้มงวด: จัด layout สำหรับทั้งทรีก่อน จากนั้นจึง paint เฟส paint สามารถข้ามได้หาก layout ไม่เปลี่ยนแปลง ซึ่งช่วยประหยัดทรัพยากร GPU
ระหว่าง layout Flutter ส่ง ข้อจำกัด (constraints) จากบนลงล่างผ่าน RenderObject Tree ผู้ปกครองแต่ละรายกำหนดความกว้างและความสูงต่ำสุดและสูงสุดให้กับออบเจ็กต์ลูก ออบเจ็กต์ลูกคำนวณขนาดของตนเองภายในข้อจำกัดเหล่านี้และส่งคืนให้ผู้ปกครอง กระบวนการนี้เรียกว่า "downward pass" (การส่งข้อจำกัดลง) และ "upward pass" (การส่งขนาดขึ้น)
หลังจาก layout เสร็จสิ้น Flutter จะดำเนินการ paint — การวาดแต่ละ RenderObject ผู้ปกครองสร้าง PaintingContext ส่งต่อไปยังออบเจ็กต์ลูกและระบุออฟเซ็ต (offset) แต่ละ RenderObject วาดตัวเองบน Canvas โดยใช้พริมิทีฟกราฟิก: สี่เหลี่ยม วงกลม ข้อความ รูปภาพ และการแปลง Canvas โต้ตอบโดยตรงกับ Skia หรือ Impeller
Flutter ใช้กลไก โหนด "สกปรก" เพื่อลดปริมาณงาน เมื่อขนาดของ RenderObject ตัวเดียวเปลี่ยนไป Flutter จะไม่คำนวณ layout ของทั้งทรีใหม่ — มันทำเครื่องหมายโหนดที่เปลี่ยนและบรรพบุรุษที่เป็นไปได้จนถึง RenderBox ที่มีขนาดคงที่ที่ใกล้ที่สุด ในทำนองเดียวกัน เมื่อลักษณะภายนอกเปลี่ยนไป เฉพาะ paint เท่านั้นที่ถูกทำเครื่องหมาย โดยไม่ต้อง layout ซ้ำ
Flutter มี RenderObject หลายประเภทสำหรับงานต่าง ๆ: RenderBox สำหรับเอลิเมนต์สี่เหลี่ยมมาตรฐาน, RenderSliver สำหรับพื้นที่เลื่อนได้ และ RenderObject แบบกำหนดเองสำหรับการเรนเดอร์ที่ไม่ได้มาตรฐานผ่าน CustomPainter แต่ละประเภทถูกปรับให้เหมาะสมกับบทบาทของตนในทรีการเรนเดอร์
RenderBox — คลาสพื้นฐานสำหรับเอลิเมนต์อินเทอร์เฟซทรงสี่เหลี่ยม ประเภทย่อย: RenderPadding (เพิ่มระยะห่าง), RenderTransform (ใช้การแปลง), RenderFlex (นำ Row และ Column ไปใช้), RenderStack (การซ้อนทับเอลิเมนต์), RenderImage (แสดงภาพ), RenderParagraph (แสดงข้อความ) แต่ละประเภทย่อยแทนที่ performLayout และ paint สำหรับตรรกะของตนเอง
RenderSliver — ประเภท RenderObject สำหรับทำงานกับพื้นที่เลื่อนได้ ต่างจาก RenderBox ตรงที่ Sliver ใช้ SliverConstraints ซึ่งรวมข้อมูลเกี่ยวกับพื้นที่มองเห็น (viewport) RenderSliverList และ RenderSliverGrid สร้าง RenderObject เฉพาะสำหรับเอลิเมนต์ที่อยู่ในพื้นที่มองเห็น ทำให้สามารถจัดการลิสต์ที่มีล้านรายการได้
สำหรับกราฟิกที่ไม่ได้มาตรฐาน ให้ใช้ CustomPaint และ CustomPainter CustomPaint สร้าง RenderCustomPaint ที่เรียกเมธอด paint ของ CustomPainter ซึ่งช่วยให้วาดรูปร่าง กราฟ และแอนิเมชันใด ๆ ได้ด้วยการควบคุม Canvas อย่างสมบูรณ์ ตาม Flutter Team, CustomPainter มีประสิทธิภาพมากกว่าวิดเจ็ตมาตรฐานแบบซ้อนสำหรับกราฟิกเวกเตอร์ที่ซับซ้อน
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 3,
paint,
);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) =>
oldDelegate.color != color;
}
ในตัวอย่างนี้ CirclePainter วาดวงกลมบน Canvas เมธอด shouldRepaint ส่งคืน true เฉพาะเมื่อสีเปลี่ยน ซึ่งป้องกันการวาดซ้ำที่ไม่จำเป็น CustomPainter ควรมีน้ำหนักเบาที่สุด — การคำนวณหนักทั้งหมดควรทำนอกเมธอด paint
RenderObject Tree ถูกสร้างจาก Element Tree ผ่านกลไก RenderObjectWidget แต่ละ RenderObjectWidget (Padding, Transform, CustomPaint) สร้าง RenderObjectElement ที่สร้างและจัดการ RenderObject ที่เกี่ยวข้อง เอลิเมนต์ทำหน้าที่เป็นตัวกลาง: ส่งคอนฟิกูเรชันจากวิดเจ็ตไปยัง RenderObject และแจ้ง RenderObject เกี่ยวกับการเปลี่ยนแปลง
เมื่อ RenderObjectElement ถูกเมาท์ มันจะเรียกเมธอด createRenderObject ของวิดเจ็ต วิดเจ็ตสร้างอินสแตนซ์ของ RenderObject และส่งกลับไปยังเอลิเมนต์ เอลิเมนต์แทรก RenderObject เข้าไปใน RenderObject Tree โดยเรียกเมธอด insertChildLayout ของ RenderObject หลัก กระบวนการนี้เกิดขึ้นเฉพาะในการเมาท์ครั้งแรกเท่านั้น — ในการอัปเดตครั้งถัดไป เอลิเมนต์เพียงแค่อัปเดตพารามิเตอร์ของ RenderObject ที่มีอยู่
เมื่อคอนฟิกูเรชันของวิดเจ็ตเปลี่ยน (เช่น ค่า padding เปลี่ยน) เอลิเมนต์เรียกเมธอด updateRenderObject ซึ่งส่งคอนฟิกูเรชันใหม่ไปยัง RenderObject ที่มีอยู่ RenderObject ทำเครื่องหมายตัวเองว่า "สกปรก" สำหรับ layout หรือ paint และในเฟรมถัดไปเฟรมเวิร์กจะดำเนินการอัปเดตที่จำเป็น
เมื่อเอลิเมนต์ถูกอันเมาท์ เมธอด unmount จะถูกเรียก ซึ่งลบ RenderObject ออกจาก RenderObject Tree และปล่อยทรัพยากร RenderObject.remove ถูกเรียกเพื่อตัดการเชื่อมต่อจากผู้ปกครอง หลังจากนั้นออบเจ็กต์สามารถถูกเก็บโดย garbage collector Flutter รับประกันว่าไม่มี RenderObject ค้างอยู่ในทรีโดยไม่มีเอลิเมนต์ที่เกี่ยวข้อง
RepaintBoundary — วิดเจ็ตที่สร้างเลเยอร์แยกต่างหากสำหรับการเรนเดอร์เนื้อหา เมื่อเนื้อหาของ RepaintBoundary เปลี่ยนแปลง เฉพาะเลเยอร์นี้เท่านั้นที่ถูกวาดซ้ำ ส่วนอื่นของหน้าจอยังคงไม่เปลี่ยนแปลง RepaintBoundary มีประโยชน์โดยเฉพาะสำหรับแอนิเมชัน เครื่องเล่นวิดีโอ กราฟแบบโต้ตอบ และเอลิเมนต์อื่น ๆ ที่อัปเดตบ่อย
หัวใจของ RepaintBoundary คือ RenderRepaintBoundary — RenderObject พิเศษที่สร้าง PictureLayer แยกต่างหาก ในการเรนเดอร์ครั้งแรก RenderRepaintBoundary บันทึกคำสั่งกราฟิกลงในเลเยอร์นี้ ในการอัปเดตครั้งถัดไป หากเฉพาะเนื้อหาภายใน RepaintBoundary เปลี่ยนแปลง Flutter จะวาดซ้ำเฉพาะเลเยอร์นี้ ไม่ใช่ทั้งหน้าจอ เลเยอร์อื่นยังคงไม่เปลี่ยนแปลงและถูกนำกลับมาใช้ใหม่
ไม่ใช่ว่าทุกวิดเจ็ตจำเป็นต้องมี RepaintBoundary ใช้เมื่อส่วนหนึ่งของอินเทอร์เฟซอัปเดตด้วย ความถี่สูง (60 FPS หรือสูงกว่า) ในขณะที่หน้าจอส่วนอื่นคงที่ ตัวอย่างทั่วไป: ตัวโหลดแบบแอนิเมชัน เครื่องเล่นวิดีโอ Canvas ของเกม CustomPainter ที่วาดซ้ำบ่อย สำหรับข้อความคงที่หรือปุ่ม RepaintBoundary ฟุ่มเฟือยและเพิ่มการใช้หน่วยความจำเท่านั้น
| สถานการณ์ | คำแนะนำ | เหตุผล |
|---|---|---|
| แอนิเมชัน | ห่อด้วย RepaintBoundary | แยกพื้นที่ที่อัปเดตบ่อย |
| ลิสต์เลื่อน | ไม่จำเป็น | ListView ใช้เลเยอร์อัตโนมัติ |
| ข้อความคงที่ | ไม่จำเป็น | ไม่มีการวาดซ้ำบ่อย |
| CustomPainter | แนะนำ | การวาดกราฟิกซ้ำบ่อย |
ประสิทธิภาพ ของ RenderObject Tree ขึ้นอยู่กับจำนวนโหนด ความลึกของทรี และความถี่ในการวาดซ้ำ เครื่องมือ Flutter DevTools (แท็บ "Rendering") ช่วยให้วิเคราะห์ RenderObject Tree แบบเรียลไทม์: จำนวน repaint, เวลา layout และ paint, จำนวนเลเยอร์และขนาด การวิเคราะห์เป็นประจำช่วยระบุจุดคอขวด
Overdraw — สถานการณ์ที่พิกเซลหนึ่งถูกวาดหลายครั้งต่อเฟรม ตัวอย่างเช่น เมื่อวิดเจ็ตโปร่งใสซ้อนทับอีกวิดเจ็ตหนึ่ง GPU จะวาดทั้งสองเลเยอร์ ใช้แฟล็ก opaque (Container พร้อมสีแทน decoration) สำหรับเอลิเมนต์ทึบแสงเพื่อให้ Flutter ข้ามเลเยอร์ที่มองไม่เห็น ตาม Flutter Team การลด overdraw สามารถลดเวลา paint ได้ถึง 30%
แต่ละ RepaintBoundary และวิดเจ็ตบางตัว (Opacity, ClipRRect, Transform) สร้าง เลเยอร์แยก (PictureLayer) เลเยอร์มากเกินไปเพิ่มเวลาในการประกอบ ใช้การจัดกลุ่ม: แทนที่จะใช้ Opacity หลายตัวบนวิดเจ็ตแยกกัน ให้ใช้ Opacity เดียวบนคอนเทนเนอร์ แทน ClipRRect บนแต่ละเอลิเมนต์ ให้ใช้ ClipRRect บนคอนเทนเนอร์ร่วม
เมื่อวิดเจ็ตถูกประกาศเป็น const Flutter รู้ว่าคอนฟิกูเรชันของมันจะไม่เปลี่ยนแปลงและสามารถนำ RenderObject ที่เกี่ยวข้องกลับมาใช้ใหม่ได้โดยไม่ต้องสร้างใหม่ const คอนสตรักเตอร์ลดภาระบน garbage collector และเร่งเฟรมแรก ใช้ const สำหรับวิดเจ็ตที่มีพารามิเตอร์คงที่: ไอคอน หัวเรื่อง เอลิเมนต์ตกแต่ง
const Text("ชื่อ", style: TextStyle(fontSize: 24));
const Icon(Icons.home, color: Colors.blue);
const SizedBox.shrink();
คำถามที่พบบ่อย
Widget Tree — คอนฟิกูเรชันน้ำหนักเบาของอินเทอร์เฟซที่ถูกสร้างใหม่ทุกครั้งที่มีการ rebuild RenderObject Tree — ทรีหนักของออบเจ็กต์การเรนเดอร์ที่คงอยู่และอัปเดตเฉพาะเมื่อเรขาคณิตหรือลักษณะภายนอกเปลี่ยนแปลง
ใช้ Flutter DevTools — แท็บ "Rendering" คุณจะเห็นเลเยอร์การเรนเดอร์ เวลา layout และ paint รวมถึงข้อมูลโดยละเอียดเกี่ยวกับแต่ละ RenderObject: ขนาด, constraints, แฟล็กความต้องการอัปเดต
Dirty — RenderObject ที่ถูกทำเครื่องหมายว่าต้องการอัปเดต แฟล็ก _needsLayout หรือ _needsPaint ถูกตั้งค่าเมื่อคอนฟิกูเรชันเปลี่ยนแปลง และ Flutter จะดำเนินการ layout หรือ paint สำหรับโหนดนี้ในเฟรมถัดไป
ได้, สร้างคลาสย่อยของ RenderBox และแทนที่เมธอด performLayout และ paint ใช้ RenderObjectWidget เพื่อแทรก RenderObject ที่กำหนดเองเข้าไปใน Widget Tree นี่เป็นเทคนิคขั้นสูงสำหรับการเรนเดอร์ที่ไม่ได้มาตรฐาน
RepaintBoundary สร้าง RenderRepaintBoundary ที่แยกส่วนของ RenderObject Tree ออกเป็นเลเยอร์แยกต่างหาก เมื่อเนื้อหาเปลี่ยนแปลง เฉพาะเลเยอร์นี้ถูกวาดซ้ำ ส่วนที่เหลือของทรียังคงไม่เปลี่ยนแปลง
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม