الودجات المخصصة والرسم المخصص

أساسيات الرسم: الخطوط والمستطيلات والدوائر

16 دقيقة الدرس 4 من 12

أساسيات الرسم: الخطوط والمستطيلات والدوائر

كل ودجت مرسوم يدوياً في Flutter يبدأ بنفس اللبنات الأساسية: كائن Canvas وكائن Paint. يُتيح Canvas مجموعة من عمليات الرسم الأولية — drawLine وdrawRect وdrawOval وdrawArc — تسمح لك بتأليف أي شيء بدءاً من فواصل بسيطة وصولاً إلى ودجات الرسوم البيانية الكاملة. في هذا الدرس ستتقن كل عملية أولية وخصائص Paint التي تتحكم في مظهرها.

كائن Paint

قبل استدعاء أي أسلوب رسم يجب عليك تهيئة نسخة من Paint. يحمل Paint جميع الخصائص البصرية لعملية الرسم. أهم الخصائص هي:

  • color — اللون المستخدم للحد أو التعبئة.
  • style — تُرسم PaintingStyle.stroke خطاً خارجياً؛ بينما تملأ PaintingStyle.fill الداخل.
  • strokeWidth — سُمك الخط بالبكسلات المنطقية (له معنى فقط عندما يكون style هو stroke).
  • isAntiAlias — يُنعّم الحواف المائلة؛ قيمته الافتراضية true ويجب في الغالب الإبقاء عليها كذلك.
  • strokeCap — كيفية رسم نهايات الخط: StrokeCap.butt أو round أو square.
ملاحظة: كائن Paint قابل للتعديل، لذا يمكنك إعادة استخدام نسخة واحدة بتغيير خصائصها بين استدعاءات الرسم. للوضوح يفضّل كثير من المطورين إنشاء Paint منفصل لكل نمط بصري (مثلاً _fillPaint و_strokePaint).

drawLine — الخطوط المستقيمة

canvas.drawLine(Offset p1, Offset p2, Paint paint) يرسم قطعة مستقيمة من p1 إلى p2. يجب أن يستخدم Paint نمط PaintingStyle.stroke؛ لأن التعبئة لا أثر لها على خط أحادي البُعد. استخدم strokeWidth للتحكم في السُمك وstrokeCap للتحكم في طريقة ظهور نهايات الخط.

رسم خطوط بأطراف مختلفة

class LineDemoPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = const Color(0xFF1565C0)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 6
      ..isAntiAlias = true;

    // طرف مسطح — ينتهي الخط عند النقطة تماماً
    paint.strokeCap = StrokeCap.butt;
    canvas.drawLine(
      const Offset(20, 40),
      Offset(size.width - 20, 40),
      paint,
    );

    // طرف مدوّر — يمتد نصف دائرة خارج نقطة النهاية
    paint.strokeCap = StrokeCap.round;
    paint.color = const Color(0xFFE53935);
    canvas.drawLine(
      const Offset(20, 90),
      Offset(size.width - 20, 90),
      paint,
    );

    // طرف مربع — يمتد مستطيل بنصف strokeWidth
    paint.strokeCap = StrokeCap.square;
    paint.color = const Color(0xFF2E7D32);
    canvas.drawLine(
      const Offset(20, 140),
      Offset(size.width - 20, 140),
      paint,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter old) => false;
}

drawRect — المستطيلات

canvas.drawRect(Rect rect, Paint paint) يرسم مستطيلاً محدداً بكائن Rect. توفر Flutter عدة بناءات مُسمّاة على Rect أكثر تعبيراً من الإحداثيات الخام:

  • Rect.fromLTWH(left, top, width, height) — الموقع والحجم.
  • Rect.fromLTRB(left, top, right, bottom) — زاويتان متقابلتان.
  • Rect.fromCenter(center: offset, width: w, height: h) — مرتكز على نقطة.
نصيحة: عندما تحتاج زوايا مدوّرة، استبدل drawRect بـ drawRRect ومرّر RRect.fromRectAndRadius. الواجهة البرمجية متطابقة عدا وسيطة نصف القطر الإضافية.

drawOval و drawCircle

canvas.drawOval(Rect rect, Paint paint) يرسم قطعاً ناقصاً منتسباً داخل المستطيل المحدد. عندما يكون المستطيل مربعاً تكون النتيجة دائرة كاملة. يوفر Flutter أيضاً للراحة canvas.drawCircle(Offset center, double radius, Paint paint) الذي يُغني عن بناء Rect.

drawArc — الأقواس وشرائح الدائرة

canvas.drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) يرسم قوساً منتسباً داخل rect. الزوايا بالـ راديان (وليس الدرجات). الموضع عند صفر راديان هو اتجاه الساعة 3. عندما تكون قيمة useCenter هي true يُرسم خطان من طرفي القوس إلى المركز مكوّنَين شكل شريحة فطيرة.

مستطيلات ودوائر وقوس في رسّام واحد

import 'dart:math' as math;

class ShapesDemoPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final fillPaint = Paint()
      ..style = PaintingStyle.fill
      ..isAntiAlias = true;

    final strokePaint = Paint()
      ..style = PaintingStyle.stroke
      ..strokeWidth = 3
      ..isAntiAlias = true;

    // مستطيل ممتلئ
    fillPaint.color = const Color(0xFFBBDEFB);
    strokePaint.color = const Color(0xFF1565C0);
    final rect = Rect.fromLTWH(20, 20, 120, 80);
    canvas.drawRect(rect, fillPaint);
    canvas.drawRect(rect, strokePaint);

    // دائرة ممتلئة
    fillPaint.color = const Color(0xFFC8E6C9);
    strokePaint.color = const Color(0xFF2E7D32);
    canvas.drawCircle(
      Offset(size.width / 2, 60),
      45,
      fillPaint,
    );
    canvas.drawCircle(
      Offset(size.width / 2, 60),
      45,
      strokePaint,
    );

    // قوس شريحة فطيرة (90 درجة = pi/2 راديان)
    fillPaint.color = const Color(0xFFFFCDD2);
    strokePaint.color = const Color(0xFFB71C1C);
    final arcRect = Rect.fromCenter(
      center: Offset(size.width - 80, 60),
      width: 90,
      height: 90,
    );
    canvas.drawArc(arcRect, 0, math.pi / 2, true, fillPaint);
    canvas.drawArc(arcRect, 0, math.pi / 2, true, strokePaint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter old) => false;
}

نظام الإحداثيات والحجم

نظام إحداثيات Canvas يضع نقطة الأصل (0, 0) في الزاوية العلوية اليسرى من الودجت. يزيد المحور السيني نحو اليمين؛ يزيد المحور الصادي نحو الأسفل. المعامل size الممرَّر إلى paint() هو حجم الودجت بالبكسلات المنطقية كما يحدده نظام التخطيط. استخدم دائماً size.width وsize.height عوضاً عن الأرقام الثابتة لكي يتكيّف الرسّام مع أي شاشة.

تحذير: الرسم خارج حدود [0,0] — [size.width, size.height] مسموح تقنياً لكنه سيتجاوز حد الودجت بصرياً. يمكن تطبيق القص بـ canvas.clipRect لإلزام الاحتواء.

الجمع بين التعبئة والحد

استدعاء رسم واحد يُطبّق Paint واحداً فقط. لتصيير داخل ممتلئ مع حد مرئي يجب استدعاء أسلوب الرسم مرتين — مرة بـ Paint للتعبئة ومرة بـ Paint للحد — على نفس الشكل الهندسي. ارسم التعبئة أولاً لكي يظهر الحد فوقها.

الخلاصة

غطّى هذا الدرس عمليات Canvas الأولية الأساسية:

  • drawLine — قطع مستقيمة؛ تتحكم فيها strokeWidth و strokeCap.
  • drawRect — مستطيلات محددة بـ Rect؛ تدعم التعبئة والحد.
  • drawOval / drawCircle — قطوع ناقصة ودوائر.
  • drawArc — أقواس مع خطوط اختيارية إلى المركز؛ الزوايا بالراديان.
  • Paint — الكائن الوحيد الذي يتحكم في اللون والنمط وسُمك الخط ومكافحة التشرذم وطرف الخط لكل عملية رسم.

في الدرس التالي ستتعلم كيفية تطبيق التحويلات (الإزاحة والتدوير والتحجيم) على Canvas لبناء تركيبات أكثر تعقيداً دون الحاجة لحساب كل إحداثي يدوياً.