CustomPainter — คืออะไร เมธอด paint และ shouldRepaint ใน Flutter

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-07-22 เวลาอ่าน: 10 นาที

CustomPainter เป็นคลาสนามธรรมจาก Flutter SDK ที่ให้นักพัฒนาควบคุมการเรนเดอร์กราฟิก 2D แบบกำหนดเองบน Canvas ได้อย่างสมบูรณ์ นักพัฒนาสร้างคลาสย่อยของ CustomPainter และใช้งานสองเมธอดที่จำเป็น: paint(Canvas, Size) และ shouldRepaint(covariant CustomPainter oldDelegate) ตามเอกสาร Flutter API (2026) CustomPainter ถูกใช้ร่วมกับวิดเจ็ต CustomPaint ซึ่งมอบหมายการเรนเดอร์ให้กับออบเจ็กต์ CustomPainter ที่ส่งให้ในแต่ละเฟรมของแอนิเมชันหรือการเปลี่ยนแปลงสถานะ

ประเด็นสำคัญ

  • CustomPainter — คลาสนามธรรมของ Flutter ที่กำหนดการเรนเดอร์แบบกำหนดเองบน Canvas ผ่านเมธอด paint และ shouldRepaint
  • CustomPaint — วิดเจ็ตที่รับ CustomPainter เป็นพารามิเตอร์ painter หรือ foregroundPainter และแสดงผลลัพธ์บนหน้าจอ
  • Canvas — ออบเจ็กต์ที่ให้เมธอดการวาด (drawLine, drawCircle, drawPath, drawRect, drawArc และอื่น ๆ)
  • shouldRepaint — เมธอดการปรับปรุงประสิทธิภาพที่กำหนดว่าออบเจ็กต์จำเป็นต้องวาดใหม่เมื่อมีการอัปเดตหรือไม่
  • size — ขนาดของพื้นที่วาดที่ส่งไปยัง paint(Canvas canvas, Size size) จากวิดเจ็ตแม่

CustomPainter คืออะไร?

CustomPainter เป็นคลาสพื้นฐานของ Flutter สำหรับการใช้งานกราฟิก 2D แบบกำหนดเอง มันแทนที่วิธีการโอเวอร์ไรด์ onDraw ใน Android โดยให้โมเดลการเรนเดอร์ที่ยืดหยุ่นและประกาศได้มากขึ้น นักพัฒนาสร้างคลาสย่อยของ CustomPainter โอเวอร์ไรด์เมธอด paint ซึ่งรับ Canvas และ Size และดำเนินการวาดทั้งหมดบน Canvas วิดเจ็ต CustomPaint จัดการการเรียก paint และรวมผลลัพธ์เข้าในทรีของวิดเจ็ต

ความแตกต่างหลักระหว่าง CustomPainter และการวาดโดยตรงผ่าน Canvas คือการแยกตรรกะการเรนเดอร์ (paint) และตัวกระตุ้นการวาดใหม่ (shouldRepaint) ระบบ Flutter เรียก shouldRepaint ทุกครั้งที่วิดเจ็ตแม่เปลี่ยนแปลง หาก shouldRepaint คืนค่า false จะไม่มีการเรียก paint ซึ่งช่วยประหยัดทรัพยากร กลไกนี้คล้ายกับ shouldRebuild ใน RenderObject และเป็นส่วนหนึ่งของสถาปัตยกรรม Flutter ที่มีสามทรี (Widget, Element, RenderObject)

CustomPaint เป็นวิดเจ็ตที่รับพารามิเตอร์ painter (เลเยอร์พื้นหลัง) และ foregroundPainter (เลเยอร์พื้นหน้า) พารามิเตอร์ทั้งสองรับออบเจ็กต์ CustomPainter painter พื้นหลังถูกวาดก่อนวิดเจ็ตลูกของ CustomPaint และ foregroundPainter ถูกวาดหลังจากนั้น ซึ่งช่วยให้ใช้ CustomPaint เป็นคอนเทนเนอร์ที่มีพื้นหลังตกแต่งหรือการซ้อนทับโดยไม่สูญเสียความสามารถในการฝังวิดเจ็ตปกติไว้ภายใน

Canvas และ Paint ใน Flutter

Canvas เป็นออบเจ็กต์กลางสำหรับการวาดใน Flutter ที่ห่อหุ้มพื้นผิวกราฟิก มันให้เมธอดการวาดประมาณ 40 เมธอด: drawLine, drawCircle, drawRect, drawPath, drawArc, drawImage, drawPicture, drawPoints และอื่น ๆ Canvas ใน Flutter สอดคล้องกับคลาสที่คล้ายกันใน Android Canvas แต่ปรับให้เข้ากับสถาปัตยกรรม Dart และ Flutter Engine Canvas ทำงานในระบบพิกัดที่จุดกำเนิด (0, 0) คือมุมซ้ายบน

Paint — อย่าสับสนกับเมธอด paint() — เป็นคลาสที่กำหนดสไตล์การวาด: สี (color), ความหนาของเส้น (strokeWidth), สไตล์ (style: fill หรือ stroke), ความโปร่งใส, โหมดการผสม (blendMode) และมาสก์การลดรอยหยัก Paint ถูกกำหนดค่าก่อนการวาดและส่งไปยังเมธอดของ Canvas สำคัญ: Paint เป็นออบเจ็กต์ที่เปลี่ยนแปลงได้ แต่แนะนำให้สร้างครั้งเดียวและนำกลับมาใช้ใหม่ โดยเปลี่ยนเฉพาะพารามิเตอร์ที่จำเป็น

เมธอด Canvasวัตถุประสงค์พารามิเตอร์
drawCircleวาดวงกลมOffset center, double radius, Paint paint
drawLineวาดส่วนของเส้นตรงOffset p1, Offset p2, Paint paint
drawRectวาดสี่เหลี่ยมผืนผ้าRect rect, Paint paint
drawPathวาดเส้นทางตามอำเภอใจPath path, Paint paint
drawArcวาดส่วนโค้งหรือภาคRect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint
drawRRectสี่เหลี่ยมผืนผ้ามุมมนRRect rrect, Paint paint
drawOvalวาดวงรีRect rect, Paint paint
drawImageวาดภาพจาก ImageImage image, Offset p, Paint paint

การแปลง Canvas

Canvas รองรับการแปลงระบบพิกัด: translate(double dx, double dy) เลื่อนจุดกำเนิด; rotate(double radians) หมุนระบบ; scale(double sx, double sy) ปรับขนาด; skew(double sx, double sy) เอียง ก่อนการแปลง แนะนำให้บันทึกสถานะ Canvas ผ่าน save() และกู้คืนหลังจากเสร็จสิ้นผ่าน restore() ซึ่งป้องกันการสะสมของการแปลงในการเรียก paint ครั้งต่อ ๆ ไป

เมธอด paint และ shouldRepaint

paint(Canvas canvas, Size size) เป็นเมธอดที่จำเป็นซึ่งใช้ตรรกะการวาดทั้งหมด พารามิเตอร์ canvas คือพื้นผิวกราฟิกสำหรับการวาด พารามิเตอร์ size คือขนาดของพื้นที่ที่จัดสรรสำหรับการวาด (ความกว้างและความสูงในพิกเซลเชิงตรรกะ) Canvas ถูกกำหนดค่าล่วงหน้าให้จุดกำเนิด (0,0) สอดคล้องกับมุมซ้ายบนของพื้นที่วาด พิกัดทั้งหมดถูกกำหนดโดยสัมพันธ์กับจุดกำเนิดนี้

shouldRepaint(covariant CustomPainter oldDelegate) เป็นเมธอดที่จำเป็นซึ่งปรับปรุงความถี่ในการวาดใหม่ Flutter เรียก shouldRepaint ทุกครั้งที่มีการอัปเดตทรีของวิดเจ็ตที่ส่งผลกระทบต่อ CustomPainter หากเมธอดคืนค่า false ภาพที่เรนเดอร์แล้วจะถือว่าใช้ได้และจะไม่มีการเรียก paint การใช้งานทั่วไป: return oldDelegate.someField != someField — การเปรียบเทียบฟิลด์ของ painter เก่าและใหม่เพื่อกำหนดความจำเป็นในการวาดใหม่

สำหรับภาพที่ เคลื่อนไหว หรือเปลี่ยนแปลงตลอดเวลา (นาฬิกา ตัวบ่งชี้การโหลด กราฟ) shouldRepaint ควรคืนค่า true ทุกครั้งที่มีการเปลี่ยนแปลงสถานะ หากออบเจ็กต์เป็นแบบคงที่ (โลโก้ ไอคอน พื้นหลัง) shouldRepaint จะคืนค่า false หลังจากการเรนเดอร์ครั้งแรก Flutter จะแคชผลลัพธ์ของ paint โดยอัตโนมัติและวาดใหม่เฉพาะเมื่อขนาด ความโปร่งใส หรือการเรียก shouldRepaint เปลี่ยนแปลง

ตัวอย่างการวาดแบบกำหนดเองด้วย CustomPainter ใน Dart

มาสร้าง GaugePainter — เกจวัดแบบวงกลมที่กำหนดเองพร้อมการเติมแบบเคลื่อนไหว CustomPainter รับค่าปัจจุบัน (ความคืบหน้าจาก 0.0 ถึง 1.0) และวาดส่วนโค้งด้วยเกรเดียนต์ เมธอด shouldRepaint เปรียบเทียบค่าความคืบหน้าเพื่อการปรับปรุงประสิทธิภาพ CustomPaint ถูกวางในทรีของวิดเจ็ตด้วยพารามิเตอร์ size เพื่อจำกัดพื้นที่วาด

dart
class GaugePainter extends CustomPainter {
    final double progress;
    final Color startColor;
    final Color endColor;

    GaugePainter({
        required this.progress,
        this.startColor = Colors.blue,
        this.endColor = Colors.cyan,
    });

    @override
    void paint(Canvas canvas, Size size) {
        final center = Offset(size.width / 2,
            size.height / 2);
        final radius = center.dx - 12;
        final bgPaint = Paint()
            ..color = Colors.grey.withOpacity(0.3)
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        final progressPaint = Paint()
            ..shader = LinearGradient(
                colors: [startColor, endColor],
            ).createShader(
                Rect.fromCircle(
                    center, radius))
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        canvas.drawCircle(center, radius, bgPaint);
        canvas.drawArc(
            Rect.fromCircle(center, radius),
            -math.pi / 2,
            2 * math.pi * progress,
            false, progressPaint);
    }

    @override
    bool shouldRepaint(
        GaugePainter oldDelegate) {
        return oldDelegate.progress != progress;
    }
}

CustomPainter กับ Path สำหรับรูปร่างที่ซับซ้อน

ตัวอย่างวาด กราฟคลื่น โดยใช้ Path Path ช่วยให้สร้างเส้นขอบตามอำเภอใจจากเส้นตรง เส้นโค้งเบซิเอร์ และส่วนโค้ง หลังจากสร้างเส้นขอบแล้ว จะถูกเติมด้วยสีหรือวาดเส้นขอบด้วยเส้น ในตัวอย่าง กราฟถูกสร้างจากอาร์เรย์ของจุดที่จำลองรูปคลื่น

dart
class WavePainter extends CustomPainter {
    final List<double> values;

    WavePainter({required this.values});

    @override
    void paint(Canvas canvas, Size size) {
        final path = Path();
        final dx = size.width /
            (values.length - 1);

        path.moveTo(0, size.height);
        for (var i = 0; i < values.length; i++) {
            final x = i * dx;
            final y = size.height *
                (1 - values[i]);
            path.lineTo(x, y);
        }
        path.lineTo(size.width, size.height);
        path.close();

        final fillPaint = Paint()
            ..color = Colors.blue.withOpacity(0.3)
            ..style = PaintingStyle.fill;

        canvas.drawPath(path, fillPaint);
    }

    @override
    bool shouldRepaint(
        WavePainter oldDelegate) {
        return oldDelegate.values != values;
    }
}

ForegroundPainter และ backgroundPainter: ความแตกต่าง

painter (พื้นหลัง) ถูกวาดก่อนวิดเจ็ตลูกของ CustomPaint หาก CustomPaint มีวิดเจ็ตลูก (พารามิเตอร์ child) painter พื้นหลังจะแสดงอยู่ข้างใต้ ซึ่งเหมาะสำหรับการตกแต่ง: แถบความคืบหน้า ลวดลายพื้นหลัง ลายน้ำ foregroundPainter ถูกวาดหลังจากวิดเจ็ตลูก นั่นคืออยู่ด้านบน ใช้สำหรับการซ้อนทับ: การเน้น มาสก์ ไม้บรรทัดวัด คำอธิบายประกอบ

ความแตกต่าง ระหว่าง painter และ foregroundPainter มีความสำคัญสำหรับลำดับ Z หากคุณต้องการวาดอะไรบางอย่างด้านหลังเนื้อหา — ใช้ painter หากอยู่ด้านบนเนื้อหา — ใช้ foregroundPainter ในบางกรณี คุณสามารถใช้ painter ทั้งสองพร้อมกัน: พื้นหลังผ่าน painter ป้ายซ้อนทับผ่าน foregroundPainter ซึ่งช่วยแยกตรรกะของเลเยอร์กราฟิกต่าง ๆ ออกเป็นสองคลาส CustomPainter แยกกัน

พารามิเตอร์ size ในเมธอด paint เหมือนกันสำหรับ painter ทั้งสองและถูกกำหนดโดยขนาดของพื้นที่ที่จัดสรรสำหรับ CustomPaint ขนาดสามารถตั้งค่าได้อย่างชัดเจนผ่านพารามิเตอร์ size ใน CustomPaint หรือโดยนัยผ่านคอนเทนเนอร์แม่ หากขนาดเป็น Size.zero จะไม่มีการเรียก paint — Flutter จะข้ามการเรนเดอร์สำหรับพื้นที่ที่มีขนาดเป็นศูนย์ ควรพิจารณาพฤติกรรมนี้เมื่อทำแอนิเมชันการปรากฏขององค์ประกอบ

การปรับปรุงประสิทธิภาพของ CustomPainter

กฎหลัก ของประสิทธิภาพ CustomPainter คือไม่สร้างออบเจ็กต์ภายในเมธอด paint Canvas, Paint, Path, Rect, Offset และออบเจ็กต์การวาดอื่น ๆ ควรถูกสร้างในคอนสตรัคเตอร์ของ CustomPainter หรือถูกแคช การสร้างออบเจ็กต์ภายใน paint นำไปสู่การจัดสรรหน่วยความจำในทุกเฟรม ซึ่งที่ 60 เฟรมต่อวินาทีทำให้เกิดการเก็บขยะบ่อยครั้งและการกระตุกของแอนิเมชัน (jank)

การแคช Path ช่วยเพิ่มความเร็วในการเรนเดอร์เส้นทางที่ซับซ้อนซ้ำ ๆ ได้อย่างมาก หากเส้นทางไม่เปลี่ยนแปลงระหว่างเฟรม (ตัวอย่างเช่น ตารางพื้นหลัง) ให้สร้าง Path ครั้งเดียวและนำกลับมาใช้หลายครั้ง RepaintBoundary เป็นวิดเจ็ตห่อหุ้มที่ป้องกันการวาดใหม่ของ CustomPainter เมื่อทรีของวิดเจ็ตด้านบนเปลี่ยนแปลง หาก CustomPainter เป็นแบบคงที่ ให้ห่อด้วย RepaintBoundary เพื่อแยกออก

สำหรับ CustomPainter ที่เคลื่อนไหวที่มีการวาดใหม่บ่อยครั้ง ให้ใช้ Ticker หรือ AnimationController กับ shouldRepaint ที่คืนค่า true ทุกครั้งที่มีการเปลี่ยนแปลงของค่าที่เคลื่อนไหว วิธีที่ดีที่สุดคือเปรียบเทียบฟิลด์ที่เคลื่อนไหวใน shouldRepaint แทนที่จะวาดใหม่ในทุกเฟรมโดยไม่จำเป็น การแคช Rect สำหรับเกรเดียนต์ที่ซับซ้อน: หากสีของเกรเดียนต์ไม่เปลี่ยนแปลง คุณสามารถแคช Shader ผ่าน Paint.shader

คำถามที่พบบ่อย

ความแตกต่างระหว่าง CustomPainter และ Canvas ใน Flutter คืออะไร?

CustomPainter เป็นคลาสสัญญาที่กำหนดว่าจะวาดอะไรและอย่างไรบน Canvas Canvas คือออบเจ็กต์การวาดเองที่ให้เมธอด (drawCircle, drawLine ฯลฯ) CustomPainter รับ Canvas ผ่านเมธอด paint และควบคุมการใช้งาน Canvas เดียวกันสามารถถูกใช้โดย CustomPainter ที่แตกต่างกันในเวลาที่แตกต่างกัน

สามารถใช้ CustomPainter โดยไม่มี CustomPaint ได้หรือไม่?

ไม่ CustomPainter ไม่มีความหมายหากไม่มี CustomPaint CustomPaint คือวิดเจ็ตที่เรียกเมธอด paint ของ CustomPainter และแสดงผลลัพธ์บนหน้าจอ หากไม่มี CustomPaint Canvas จะไม่ถูกสร้างและ paint จะไม่ถูกเรียกใช้งาน อย่างไรก็ตาม คุณสามารถใช้งาน RenderObject ของตัวเองด้วย Canvas ได้ แต่นี่เป็นวิธีการระดับต่ำกว่า

จะทำให้ CustomPainter เคลื่อนไหวได้อย่างไร?

ใช้ AnimationController กับ Ticker โดยส่งค่าที่เคลื่อนไหวไปยัง CustomPainter ผ่านพารามิเตอร์ของคอนสตรัคเตอร์ เมื่อค่าเปลี่ยน ให้เรียก setState() ในวิดเจ็ตที่มี CustomPaint ซึ่งจะกระตุ้นการเรียก shouldRepaint หาก shouldRepaint คืนค่า true ระบบจะเรียก paint สำหรับเฟรมแอนิเมชันใหม่

ทำไม CustomPainter ไม่อัปเดตเมื่อข้อมูลเปลี่ยน?

สาเหตุเกือบตลอดเวลาคือ shouldRepaint คืนค่า false ตรวจสอบว่าฟิลด์ของ oldDelegate ถูกเปรียบเทียบกับค่าใหม่อย่างถูกต้องหรือไม่ หากใช้ออบเจ็กต์ที่เปลี่ยนแปลงได้ shouldRepaint อาจไม่ตรวจจับการเปลี่ยนแปลงเนื่องจากการอ้างอิงออบเจ็กต์ยังคงเหมือนเดิม ใช้ == หรือออบเจ็กต์ที่ไม่เปลี่ยนสถานะเพื่อการเปรียบเทียบที่ถูกต้อง

สามารถใช้ CustomPainter สำหรับแอนิเมชันใน ReorderableListView ได้หรือไม่?

ได้ CustomPainter ใช้สำหรับเรนเดอร์การตกแต่งแบบกำหนดเองในรายการที่ซับซ้อน อย่างไรก็ตาม โปรดทราบว่าเมื่อจัดเรียงรายการใหม่ RepaintBoundary ภายใน ListView จะแคชการเรนเดอร์ของแต่ละรายการ CustomPainter ควรมีน้ำหนักเบาและไม่มีการดำเนินการหนักใน paint เพื่อไม่ให้ช้าลงแอนิเมชันการลาก

สรุป

  • CustomPainter — คลาสนามธรรมของ Flutter สำหรับการเรนเดอร์ 2D แบบกำหนดเองด้วยเมธอด paint และ shouldRepaint
  • CustomPaint — วิดเจ็ตที่รับ painter (พื้นหลัง) และ foregroundPainter (พื้นหน้า) สำหรับแสดงกราฟิก
  • Canvas — ออบเจ็กต์ที่มีเมธอดการวาด: drawLine, drawCircle, drawPath, drawRect, drawArc และอื่น ๆ
  • Paint — คลาสที่กำหนดสไตล์การวาด: สี ความหนา สไตล์ (fill/stroke) ความโปร่งใส และโหมดการผสม
  • shouldRepaint — เมธอดการปรับปรุงประสิทธิภาพที่คืนค่า true เฉพาะเมื่อฟิลด์ที่ส่งผลต่อการเรนเดอร์เปลี่ยน
  • การสร้างออบเจ็กต์ภายใน paint ถูกห้าม — Paint, Path, Rect ทั้งหมดต้องถูกแคชเพื่อ 60 fps
  • RepaintBoundary — วิดเจ็ตสำหรับแยก CustomPainter จากการวาดใหม่ของทรีวิดเจ็ตด้านบน

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม