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

بناء المسارات ومنحنيات بيزيه

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

بناء المسارات ومنحنيات بيزيه

تُمثّل كلاس Path العمود الفقري لرسم الأشكال المعقدة في نظام الرسم المخصص في Flutter. بدلاً من الاعتماد على الأشكال الأولية المحددة مسبقاً كالمستطيلات والدوائر، تقوم ببناء أشكال اعتباطية عن طريق إصدار تسلسل من أوامر الرسم — تحريك قلم افتراضي، ورسم خطوط، واكتساح منحنيات — ثم تسليم المسار المنتهي إلى canvas.drawPath() لإجراء استدعاء رسم واحد وفعّال.

كائن Path ونظام الإحداثيات

يُعدّ Path وصفاً لمحيط واحد أو أكثر (خطوط مغلقة أو مفتوحة). يضع نظام إحداثيات القماش نقطة الأصل (0, 0) في الزاوية العلوية اليسرى من منطقة الرسم، مع زيادة x نحو اليمين وزيادة y نحو الأسفل. كل إحداثي تمرره لطرق Path يكون بالبكسل المنطقي نسبةً إلى تلك الأصل.

  • moveTo(x, y) — يرفع القلم ويضع نقطة بداية جديدة دون رسم أي شيء. استخدمه لبدء محيط جديد أو لبدء مسار فرعي منفصل.
  • lineTo(x, y) — يرسم خطاً مستقيماً من النقطة الحالية إلى (x, y).
  • quadraticBezierTo(cpX, cpY, x, y) — يرسم منحنى بيزيه تربيعياً باستخدام نقطة تحكم واحدة (cpX, cpY) وينتهي عند (x, y).
  • cubicTo(cp1X, cp1Y, cp2X, cp2Y, x, y) — يرسم منحنى بيزيه تكعيبياً باستخدام نقطتَي تحكم وينتهي عند (x, y).
  • close() — يربط النقطة الحالية بنقطة بداية المحيط الحالي بخط مستقيم، مُغلقاً الشكل.
ملاحظة: المسار ذو حالة — كل أمر يستمر من حيث انتهى السابق. استدعِ دائماً moveTo قبل أول أمر رسم، وإلا سيبدأ Flutter ضمنياً من (0, 0).

رسم مثلث مخصص

أبسط مسار مغلق يمكنك بناؤه هو مثلث. انتقل إلى القمة، ارسم خطين إلى زوايا القاعدة، ثم أغلق المسار:

مثلث باستخدام moveTo وlineTo وclose

class TrianglePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = const Color(0xFF1976D2)
      ..style = PaintingStyle.fill;

    final path = Path()
      ..moveTo(size.width / 2, 0)          // القمة (المركز الأعلى)
      ..lineTo(size.width, size.height)    // أسفل اليمين
      ..lineTo(0, size.height)             // أسفل اليسار
      ..close();                           // العودة إلى القمة

    canvas.drawPath(path, paint);
  }

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

لاحظ عامل التسلسل (..) المطبَّق على منشئ Path(). كل طريقة على Path تُعيد void، لكن التسلسل يجعل الكود موجزاً بتسلسل الاستدعاءات على نفس الكائن.

منحنيات بيزيه التربيعية

يُعرَّف منحنى بيزيه التربيعي بثلاث نقاط: نقطة البداية الضمنية (موضع القلم الحالي)، نقطة تحكم واحدة، ونقطة النهاية. "يُجذب" المنحنى نحو نقطة التحكم دون المرور بها. هذا يجعل المنحنيات التربيعية مثالية للأقواس السلسة وأشكال الموجات وفقاعات الكلام.

نصيحة: تخيّل نقطة التحكم كمغناطيس: ينحني المنحنى نحوها. وضع نقطة التحكم بعيداً عن خط الأساس يُنشئ قوساً حاداً؛ وضعها قريباً يُعطي انتفاخاً خفيفاً.

لافتة موجية باستخدام quadraticBezierTo

class WavePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = const Color(0xFF26A69A)
      ..style = PaintingStyle.fill;

    final path = Path()
      ..moveTo(0, size.height * 0.6)
      // قمة الموجة الأولى
      ..quadraticBezierTo(
        size.width * 0.25, size.height * 0.4, // نقطة التحكم
        size.width * 0.5,  size.height * 0.6, // نقطة النهاية
      )
      // قمة الموجة الثانية
      ..quadraticBezierTo(
        size.width * 0.75, size.height * 0.8, // نقطة التحكم
        size.width,        size.height * 0.6, // نقطة النهاية
      )
      ..lineTo(size.width, size.height)
      ..lineTo(0, size.height)
      ..close();

    canvas.drawPath(path, paint);
  }

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

منحنيات بيزيه التكعيبية

يُدخل منحنى بيزيه التكعيبي نقطة تحكم ثانية، مما يمنحك تحكماً مستقلاً في زوايا دخول وخروج المنحنى. هذه الدرجة الإضافية من الحرية هي ما تستخدمه أدوات التصميم المتجهي مثل Figma وIllustrator للمنحنيات المركبة السلسة.

  • نقطة التحكم الأولى تتحكم في اتجاه المماس عند بداية المنحنى.
  • نقطة التحكم الثانية تتحكم في اتجاه المماس عند نهاية المنحنى.
  • وضع كلتا نقطتَي التحكم على نفس جانب المسار يُنشئ منحنى S؛ وضعهما بتناظر يُنشئ قوساً متوازناً.

شكل قطرة الماء باستخدام cubicTo

class TeardropPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = const Color(0xFFE53935)
      ..style = PaintingStyle.fill;

    final cx = size.width / 2;

    final path = Path()
      ..moveTo(cx, size.height)           // طرف الأسفل
      ..cubicTo(
        0,  size.height * 0.7,            // cp1: أسفل اليسار
        0,  size.height * 0.1,            // cp2: أعلى اليسار
        cx, 0,                            // الأعلى
      )
      ..cubicTo(
        size.width, size.height * 0.1,    // cp1: أعلى اليمين
        size.width, size.height * 0.7,    // cp2: أسفل اليمين
        cx, size.height,                  // طرف الأسفل مجدداً
      )
      ..close();

    canvas.drawPath(path, paint);
  }

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

دمج أجزاء المسار

تمزج الأشكال في العالم الحقيقي بين جميع الأوامر الأربعة بحرية. يمكن لـ Path واحد أن يحتوي على استدعاءات moveTo متعددة لإنشاء مسارات فرعية (محاور منفصلة غير متصلة). يُقدّمها Flutter في استدعاء drawPath واحد، وهو أكثر كفاءة بكثير من استدعاءات الرسم الفردية المتعددة.

تحذير: يربط استدعاء close() بنقطة بداية المحيط الحالي فقط، وليس النقطة الأولى من المسار بأكمله. إذا استخدمت moveTo لبدء مسار فرعي جديد، فإن close() يُغلق ذلك المسار الفرعي بشكل مستقل.

ملخص

تمنحك واجهة برمجة Path تحكماً دقيقاً وتعبيرياً في الأشكال المتجهية في Flutter. الطرق الرئيسية هي moveTo (الموضع)، وlineTo (المقاطع المستقيمة)، وquadraticBezierTo (منحنيات بنقطة تحكم واحدة)، وcubicTo (منحنيات بنقطتَي تحكم)، تُنهيها بـ close() وتُقدّمها عبر canvas.drawPath(path, paint). إتقان هذه الأوليات الأربع يتيح لك بناء أي شكل تقريباً — أيقونات ورسوم بيانية وخلفيات زخرفية ومكونات واجهة مستخدم مخصصة — دون استيراد أي ملف صورة.