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

مناطق القص ومسارات القطع

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

مناطق القص ومسارات القطع

يرسم Flutter كل ودجت داخل صندوق إحاطة مستطيل بشكل افتراضي. يتيح لك القص (Clipping) تقييد هذا الرسم بشكل اعتباطي — مستطيل ذو حواف مدورة، أو دائرة، أو نجمة، أو أي مسار مخصص — دون التأثير على هندسة التخطيط نفسها. تصبح المنطقة المقصوصة شفافة ببساطة؛ والبكسلات الخارجة عن حدود القص لا تُرسم.

يأتي Flutter مزوداً بأربعة ودجات قص مخصصة (ClipRect، وClipRRect، وClipOval، وClipPath)، كما يوفر canvas.clipPath() داخل CustomPainter لتحكم أدق أثناء الرسم المخصص.

ملاحظة: القص هو عملية رسم وليس عملية تخطيط. تحتل الودجت المقصوصة صندوق الإحاطة الكامل الخاص بها لأغراض اختبار النقرات والتخطيط. إن كنت بحاجة لتقليص مساحة التخطيط، ادمج القص مع SizedBox أو OverflowBox.

ClipRect — قص مستطيل

يقيّد ClipRect الرسم بالمستطيل المحيط بالودجت نفسه. يُستخدم بشكل رئيسي لمنع تأثيرات الفيض — مثلاً عندما تنزلق ودجت فرعية من خارج حدود الأب أثناء الرسوم المتحركة، أو عندما تتجاوز ودجت داخل Stack حدودها.

ClipRect لإخفاء الفيض

ClipRect(
  child: Align(
    alignment: Alignment.topCenter,
    heightFactor: 0.5,   // عرض النصف العلوي فقط
    child: Image.network(
      'https://example.com/photo.jpg',
      width: 300,
      height: 200,
      fit: BoxFit.cover,
    ),
  ),
)

ClipRRect — قص مستطيل بحواف مدورة

يقص ClipRRect العنصر الفرعي بمستطيل ذي حواف مدورة يُحدَّد بـ BorderRadius. هذه هي الطريقة المعيارية لمنح أي ودجت — صور، حاويات، بطاقات — حواف مدورة دون الحاجة لتغليفها في Container مع BoxDecoration.

ClipRRect لصورة ذات حواف مدورة

ClipRRect(
  borderRadius: BorderRadius.circular(20),
  child: Image.network(
    'https://example.com/avatar.jpg',
    width: 120,
    height: 120,
    fit: BoxFit.cover,
  ),
)

ClipOval — قص دائري أو بيضاوي

يقص ClipOval العنصر الفرعي بأكبر قطع ناقص يتناسب مع صندوق الإحاطة. عندما يكون الصندوق مربعاً، تكون النتيجة دائرة مثالية — وهو الشكل الكلاسيكي لصورة الملف الشخصي.

ClipOval لصورة ملف شخصي دائرية

ClipOval(
  child: Image.network(
    'https://example.com/profile.jpg',
    width: 80,
    height: 80,
    fit: BoxFit.cover,
  ),
)
نصيحة: فضّل ClipOval على ClipRRect(borderRadius: BorderRadius.circular(radius)) عندما تريد دائرة حقيقية، لأن ClipOval يتكيف تلقائياً مع حجم الودجت الفعلي دون الحاجة لتحديد قيمة نصف القطر يدوياً.

ClipPath — قص بشكل اعتباطي

ClipPath هو أقوى ودجت قص. يقبل CustomClipper<Path> تعيد دالة getClip() فيه أي كائن Path مبني من خطوط وأقواس ومنحنيات مكعبة أو عمليات Path.combine().

ClipPath قطري مخصص

class DiagonalClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(0, size.height - 60);     // حافة أسفل اليسار
    path.lineTo(size.width, size.height); // أسفل اليمين
    path.lineTo(size.width, 0);           // أعلى اليمين
    path.close();
    return path;
  }

  @override
  bool shouldReclip(DiagonalClipper oldClipper) => false;
}

// الاستخدام
ClipPath(
  clipper: DiagonalClipper(),
  child: Container(
    height: 200,
    color: Colors.deepPurple,
    child: const Center(
      child: Text(
        'ترويسة قطرية',
        style: TextStyle(color: Colors.white, fontSize: 24),
      ),
    ),
  ),
)
تحذير: يجب أن تعيد shouldReclip() القيمة true كلما اعتمد شكل القص على بيانات قابلة للتغيير (مثل قيمة رسوم متحركة). إعادة false بشكل غير مشروط صحيح فقط حين يكون شكل القص ثابتاً.

canvas.clipPath() داخل CustomPainter

عندما تحتاج لتطبيق قص كجزء من تسلسل رسم مخصص أشمل — مثلاً لتقييد تدرج لوني أو شكل مرسوم معقد — استدعِ canvas.clipPath(path) (أو canvas.clipRect() / canvas.clipRRect()) مباشرةً داخل CustomPainter.paint(). يؤثر القص على جميع عمليات الرسم اللاحقة على تلك اللوحة حتى تحفظ/تستعيد حالة اللوحة.

canvas.clipPath في CustomPainter

class WavePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // بناء مسار قص على شكل موجة
    final clipPath = Path();
    clipPath.lineTo(0, size.height * 0.75);
    clipPath.quadraticBezierTo(
      size.width * 0.25, size.height,
      size.width * 0.5,  size.height * 0.75,
    );
    clipPath.quadraticBezierTo(
      size.width * 0.75, size.height * 0.5,
      size.width,        size.height * 0.75,
    );
    clipPath.lineTo(size.width, 0);
    clipPath.close();

    // تطبيق القص — جميع عمليات الرسم أدناه تحترم هذا الحد
    canvas.clipPath(clipPath);

    // رسم مستطيل متدرج؛ سيُقنَّع بشكل الموجة
    final rect = Offset.zero & size;
    final paint = Paint()
      ..shader = const LinearGradient(
        colors: [Color(0xFF6A11CB), Color(0xFF2575FC)],
      ).createShader(rect);
    canvas.drawRect(rect, paint);
  }

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

اعتبارات الأداء

  • تقبل جميع ودجات القص معاملاً clipBehavior (الافتراضي: Clip.antiAlias). استخدم Clip.hardEdge لأقصى أداء حين لا تكون الحواف الناعمة ضرورية.
  • تغليف شجرة فرعية بقص يفرض إنشاء طبقة جديدة، مما يضيف تكلفة تركيب GPU. تجنب القصوص غير الضرورية في شجرات فرعية تُعاد بناؤها كثيراً.
  • استخدم RepaintBoundary حول الودجات المقصوصة التي تُعاد رسمها كثيراً لعزل تكلفة إعادة الرسم.

الملخص

يمنحك نظام القص في Flutter تحكماً دقيقاً في حدود الودجات دون تغيير التخطيط. استخدم ClipRect لإخفاء الفيض، وClipRRect للحواف المدورة، وClipOval للدوائر والأشكال البيضاوية، وClipPath (مدعوم بـ CustomClipper<Path>) لأي شكل اعتباطي. للرسم المخصص الكامل، استدعِ canvas.clipPath() داخل CustomPainter.paint() لتقييد عمليات الرسم بالشكل الذي تحدده. دائماً اضبط shouldReclip() بشكل صحيح واختر سلوك Clip المناسب للتوازن بين جودة العرض وأداء التصيير.