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 Documentation (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 مقدار فعلی (progress از 0.0 تا 1.0) را دریافت می‌کند و یک قوس با گرادیان ترسیم می‌کند. در متد shouldRepaint مقدار progress برای بهینه‌سازی مقایسه می‌شود. 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 امکان ساخت خطوط دلخواه از خطوط، منحنی‌های Bezier و قوس‌ها را فراهم می‌کند. پس از ساخت، خط با رنگ پر یا با خط حاشیه‌گذاری می‌شود. در مثال، نمودار بر اساس آرایه‌ای از نقاط که شکل موج را شبیه‌سازی می‌کنند ساخته می‌شود.

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-order حیاتی است. اگر نیاز به ترسیم چیزی در پشت محتوا دارید — از painter استفاده کنید. اگر روی محتوا — foregroundPainter. در برخی موارد می‌توان از هر دو painter به طور همزمان استفاده کرد: پس‌زمینه از طریق painter، برچسب-رویه از طریق foregroundPainter. این امکان جداسازی منطق لایه‌های گرافیکی مختلف را به دو کلاس جداگانه CustomPainter فراهم می‌کند.

پارامتر size در متد paint برای هر دو painter یکسان است و با اندازه منطقه اختصاص داده شده برای CustomPaint تعیین می‌شود. اندازه می‌تواند به طور صریح از طریق پارامتر size در CustomPaint یا به طور ضمنی از طریق کانتینر والد تعیین شود. اگر size برابر 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 منتقل کنید. هنگام تغییر مقدار، در ویجت حاوی CustomPaint setState() را فراخوانی کنید که 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 از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید