CustomPainter — یک کلاس انتزاعی از Flutter SDK است که به توسعهدهندگان کنترل کامل بر روی ترسیم گرافیک 2D سفارشی روی Canvas میدهد. توسعهدهنده زیرکلاس CustomPainter را ایجاد میکند و دو متد اجباری را پیادهسازی میکند: paint(Canvas, Size) و shouldRepaint(covariant CustomPainter oldDelegate). بر اساس Flutter API Documentation (2026)، CustomPainter همراه با ویجت CustomPaint استفاده میشود که ترسیم را به شی 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 — شیء مرکزی برای ترسیم در 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 | ترسیم تصویر از Image | Image image, Offset p, Paint paint |
Canvas از تبدیلات سیستم مختصات پشتیبانی میکند: translate(double dx, double dy) مبدأ مختصات را جابجا میکند؛ rotate(double radians) سیستم را میچرخاند؛ scale(double sx, double sy) مقیاس را تغییر میدهد؛ skew(double sx, double sy) مایل میکند. قبل از تبدیل توصیه میشود وضعیت Canvas را از طریق save() ذخیره کرده و پس از اتمام از طریق restore() بازیابی کنید. این کار از انباشته شدن تبدیلات در فراخوانیهای بعدی paint جلوگیری میکند.
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 دوباره ترسیم میکند.
بیایید GaugePainter را ایجاد کنیم — یک نشانگر دایرهای سفارشی با پر شدن متحرک. CustomPainter مقدار فعلی (progress از 0.0 تا 1.0) را دریافت میکند و یک قوس با گرادیان ترسیم میکند. در متد shouldRepaint مقدار progress برای بهینهسازی مقایسه میشود. CustomPaint در درخت ویجت با پارامتر size برای محدود کردن منطقه ترسیم قرار میگیرد.
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;
}
}
مثال یک نمودار موجی را با استفاده از Path ترسیم میکند. Path امکان ساخت خطوط دلخواه از خطوط، منحنیهای Bezier و قوسها را فراهم میکند. پس از ساخت، خط با رنگ پر یا با خط حاشیهگذاری میشود. در مثال، نمودار بر اساس آرایهای از نقاط که شکل موج را شبیهسازی میکنند ساخته میشود.
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;
}
}
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 — ایجاد نکردن اشیاء در داخل متد 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 ترسیم شود. Canvas — خود شیء ترسیم است که متدها (drawCircle، drawLine و غیره) را ارائه میدهد. CustomPainter Canvas را از طریق متد paint دریافت میکند و استفاده از آن را مدیریت میکند. یک Canvas میتواند توسط CustomPainterهای مختلف در زمانهای مختلف استفاده شود.
خیر، CustomPainter بدون CustomPaint معنی ندارد. CustomPaint ویجتی است که متد paint CustomPainter را فراخوانی کرده و نتیجه را روی صفحه نمایش میدهد. بدون CustomPaint Canvas ایجاد نمیشود و paint اجرا نمیشود. با این حال، میتوان RenderObject خود را با Canvas پیادهسازی کرد، اما این یک رویکرد سطح پایینتر است.
از AnimationController با Ticker استفاده کنید و مقدار متحرک را از طریق پارامترهای سازنده به CustomPainter منتقل کنید. هنگام تغییر مقدار، در ویجت حاوی CustomPaint setState() را فراخوانی کنید که shouldRepaint را فعال میکند. اگر shouldRepaint true برگرداند، سیستم paint را برای فریم جدید انیمیشن فراخوانی میکند.
دلیل تقریباً همیشه shouldRepaint است که false برمیگرداند. بررسی کنید که آیا فیلدهای oldDelegate به درستی با مقادیر جدید مقایسه میشوند. اگر از شیء قابل تغییر استفاده شود، shouldRepaint ممکن است تغییرات را متوجه نشود زیرا مرجع به شیء ثابت میماند. برای مقایسه صحیح از == یا اشیاء غیرقابل تغییر استفاده کنید.
بله، CustomPainter برای ترسیم تزئینات سفارشی در لیستهای پیچیده استفاده میشود. با این حال، توجه داشته باشید که هنگام مرتبسازی مجدد عناصر، RepaintBoundary داخل ListView ترسیم هر عنصر را ذخیره میکند. CustomPainter باید سبک باشد و عملیات سنگین در paint نداشته باشد تا انیمیشن کشیدن را کند نکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید