CustomPainter — ما هو، طريقتي paint و shouldRepaint في Flutter

المؤلف: IT Sectr نُشر: 2026-07-22 وقت القراءة: 10 دق

CustomPainter هي فئة مجردة من Flutter SDK تمنح المطورين تحكمًا كاملاً في عرض الرسومات ثنائية الأبعاد المخصصة على 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 لتنفيذ الرسومات ثنائية الأبعاد المخصصة. تحل محل نهج تجاوز 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() — هو فئة تُحدد نمط الرسم: اللون، سمك الخط، النمط (تعبئة أو حدود)، الشفافية، وضع المزج، وقناع التنعيم. 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 بدلاً من إعادة الرسم في كل إطار دون داعٍ. التخزين المؤقت للمستطيل للتدرجات المعقدة: إذا لم تتغير ألوان التدرج، يمكنك تخزين 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 للعرض ثنائي الأبعاد المخصص بطريقتي paint و shouldRepaint
  • CustomPaint — عنصر واجهة يقبل painter (خلفية) و foregroundPainter (مقدمة) لعرض الرسومات
  • Canvas — كائن بطرق رسم: drawLine و drawCircle و drawPath و drawRect و drawArc وغيرها
  • Paint — فئة تُحدد نمط الرسم: اللون، السمك، النمط (تعبئة/حدود)، الشفافية ووضع المزج
  • shouldRepaint — طريقة تحسين تُعيد true فقط عند تغير الحقول المؤثرة على العرض
  • إنشاء كائنات داخل paint ممنوع — يجب تخزين جميع Paint و Path و Rect مؤقتًا لتحقيق 60 إطارًا في الثانية
  • RepaintBoundary — عنصر واجهة لعزل CustomPainter عن إعادة الرسم لشجرة عناصر الواجهة الأعلى

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا