العروض المخصصة هي أساس الواجهة الفريدة لأي تطبيق محمول. تتيح العروض المخصصة في التطبيقات المحمولة إنشاء رسومات ورسوم متحركة وعناصر خاصة غير متوفرة في المكونات القياسية. وفقًا لـ Apple Developer، 2025، يظل drawRect الطريقة الأساسية للرسم المخصص في تطوير iOS المحمول. على Android، يؤدي onDraw مع Canvas نفس الدور، بينما يستخدم Flutter CustomPainter.
أهم النقاط
العروض المخصصة هي مكونات واجهة يحددها المستخدم تعيد تعريف طرق الرسم والقياس القياسية لإنشاء رسومات فريدة. الرسوم البيانية والرسوم المتحركة والمخططات وعناصر الدردشة المخصصة وأقنعة AR — هذه سيناريوهات نموذجية لاستخدام العروض المخصصة في التطبيقات المحمولة. تغطي مكونات واجهة المستخدم القياسية (Button و TextView و Label) 80% من المهام، لكن الـ 20% المتبقية تتطلب تحكمًا كاملاً في الرسم في التطوير المحمول. الرسم في التطوير المحمول هو مهارة تميز المطور المبتدئ عن الخبير.
العروض المخصصة في التطبيقات المحمولة ضرورية لـ الرسومات ثنائية الأبعاد الفريدة والرسوم المتحركة المعقدة والمخططات وتصور البيانات. إذا كان UIKit القياسي لا يوفر العنصر المطلوب — يتم إنشاء عرض مخصص. في التطبيقات المحمولة، تُستخدم العروض المخصصة لأشرطة التقدم والأزرار المخصصة والتعبئات المتدرجة والأيقونات المتحركة ولوحات الرسم بالأصابع. من المهم عدم الإفراط في الاستخدام — للأنماط البسيطة، يكفي تكوين المكونات القياسية عبر Appearance API.
على iOS، يتم إنشاء العروض المخصصة عن طريق وراثة UIView وإعادة تعريف طريقة drawRect. يتم استدعاء drawRect عند العرض الأول وبعد كل استدعاء setNeedsDisplay(). داخل drawRect، يحصل المطور على CGContext عبر UIGraphicsGetCurrentContext() — لوحة للخطوط والأشكال والتدرجات والنص. Core Graphics (Quartz 2D) هو محرك عرض منخفض المستوى على CPU يمنح تحكمًا كاملاً في كل بكسل. UIBezierPath هو غلاف مناسب لإنشاء المسارات ببناء جملة موجز.
class CircleView: UIView {
private var fillColor: UIColor = .systemBlue
override func drawRect(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.setFillColor(fillColor.cgColor)
ctx.addEllipse(in: bounds.insetBy(dx: 4, dy: 4))
ctx.drawPath(using: .fill)
}
func updateColor(_ newColor: UIColor) {
fillColor = newColor
setNeedsDisplay()
}
}
يوفر Core Graphics CGContext — لوحة لجميع عمليات الرسم. يقوم CGGradient بإنشاء تدرجات خطية وشعاعية مع دعم CGColorSpace (sRGB، Display P3). تتم التحويلات عبر CGAffineTransform — مصفوفة 3x3 للتدوير والقياس والإزاحة. تقوم CGContextTranslateCTM و CGContextRotateCTM و CGContextScaleCTM بتعديل نظام الإحداثيات. يعمل Core Graphics على CPU، لذا يمكن للرسومات المعقدة أن تثقل المعالج — استخدم Metal لتسريع GPU.
UIBezierPath هو غلاف كائني التوجه فوق CGPath. تنشئ طريقة addArc(withCenter:radius:startAngle:endAngle:clockwise:) أقواسًا ودوائر، وتنشئ addCurve منحنيات Bézier. تدعم المسارات عمليات الاتحاد والتقاطع والطرح عبر addClip. UIBezierPath مناسب للأقنعة المخصصة (maskView) ومسارات القص والأشكال المعقدة — النجوم والقلوب والأيقونات المخصصة.
على Android، يرث العرض المخصص View مع إعادة تعريف onDraw(Canvas). يحتوي Canvas على جميع طرق الرسم في التطوير المحمول: drawCircle و drawRect و drawLine و drawText و drawBitmap و drawPath و drawRoundRect. يقوم Paint بتكوين المعلمات — اللون وسمك الخط والنمط (FILL و STROKE و FILL_AND_STROKE) والتنعيم (isAntiAlias) وحجم النص. يتم استدعاء onDraw في كل مرة عند invalidate() ويجب ألا يحتوي على عمليات ثقيلة — يتم تخزين جميع الحسابات مؤقتًا.
class CustomCircleView(context: Context, attrs: AttributeSet?)
: View(context, attrs) {
private val paint = Paint().apply {
style = Paint.Style.FILL
color = Color.BLUE
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val cx = width / 2f
val cy = height / 2f
val radius = minOf(cx, cy) - 4f
canvas.drawCircle(cx, cy, radius, paint)
}
}
Path على Android هو المكافئ لـ UIBezierPath: addCircle و addRect و addOval و lineTo و cubicTo و quadTo. يقوم PathMeasure بحساب طول المسار والإحداثيات في أي نقطة — مفيد لتحريك حركة كائن على طول مسار. للرسوم المتحركة، يُستخدم ValueAnimator مع invalidate() في رد النداء. يمكن للمُحرك تغيير اللون ونصف القطر والموضع — أي خاصية تؤثر على الرسم. تدعم العروض المخصصة على Android أيضًا StateListDrawable و animated-vector للرسوم المتحركة عبر XML.
يتم تعيين التدرجات على Android عبر Shader — LinearGradient و RadialGradient و SweepGradient. يتم تعيين Shader إلى Paint عبر paint.setShader(shader). يقوم LinearGradient بإنشاء تدرج خطي بين نقطتين مع مواضع لون اختيارية. RadialGradient — شعاعي من المركز إلى الحواف. SweepGradient — مخروطي حول نقطة مركزية. يحدد Shader.TileMode (CLAMP و REPEAT و MIRROR) السلوك خارج حدود المنطقة.
يستخدم Flutter CustomPainter — فئة بطريقتي paint(Canvas canvas, Size size) و shouldRepaint. يوفر Canvas في Flutter نفس الطرق الموجودة في Android: drawCircle و drawRect و drawLine و drawPath و drawParagraph و drawImage. يقوم Paint في Flutter بتكوين اللون والسمك والنمط والتنعيم. لا يستخدم Flutter drawRect أو onDraw — كل الرسم يتم عبر CustomPainter المضمن في عنصر واجهة CustomPaint.
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill
..isAntiAlias = true;
final radius = size.shortestSide / 2 - 4;
canvas.drawCircle(size.center(Offset.zero), radius, paint);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) =>
oldDelegate.color != color;
}
يحدد shouldRepaint ما إذا كان CustomPainter بحاجة إلى إعادة الرسم. قم بإرجاع true فقط عندما تتغير البيانات المؤثرة على العرض — اللون والحجم وبيانات الرسم البياني. يعزل RepaintBoundary إعادة الرسم في طبقة منفصلة — استخدمه لعناصر واجهة المستخدم التي نادرًا ما تتغير. لا يحتوي Flutter على setNeedsDisplay أو invalidate — يتم تشغيل إعادة الرسم عبر setState أو ValueNotifier مع الاستماع للتغييرات.
يدعم Flutter LinearGradient و RadialGradient و SweepGradient — نفس الأنواع الموجودة في Android. يقوم Gradient بإنشاء Shader لـ Paint عبر createShader(Rect bounds). تتم التحويلات عبر canvas.rotate و canvas.scale و canvas.translate و canvas.skew. يستخدم Flutter مصفوفة تحويل 4x4 (Matrix4) لعنصر واجهة Transform. تتم رسوم الرسم المتحركة في Flutter عبر AnimationController مع setState لإعادة رسم CustomPainter.
القياس الصحيح للعروض المخصصة هو أساس السلوك المتوقع للواجهة. على iOS، يتم تحديد حجم العرض عبر sizeThatFits و intrinsicContentSize. يُرجع sizeThatFits الحجم الأمثل للقيود المحددة. intrinsicContentSize — الحجم الطبيعي للمحتوى دون قيود خارجية. على Android، يتم تنفيذ القياس في onMeasure مع معلمات MeasureSpec (UNSPECIFIED و EXACTLY و AT_MOST). في Flutter، يتم تمرير الحجم إلى paint(Canvas, Size) ويمكن حسابه عبر computeDryLayout أو LayoutBuilder.
يُرجع intrinsicContentSize CGSize — الحجم الطبيعي للعرض بناءً على محتواه. يتحكم setContentHuggingPriority و setContentCompressionResistancePriority في السلوك أثناء التمدد والضغط. يتم استدعاء sizeThatFits من قبل النظام لحساب الحجم في Auto Layout. للعروض المخصصة، أعد تعريف intrinsicContentSize إذا كان للمكون الخاص بك حجم طبيعي — على سبيل المثال، يجب أن تكون الدائرة دائمًا 100x100.
يقبل onMeasure widthMeasureSpec و heightMeasureSpec. يحتوي MeasureSpec على الوضع والحجم: UNSPECIFIED (بدون قيود، يختار العرض حجمه بنفسه)، EXACTLY (حجم دقيق من الأصل)، AT_MOST (الحجم الأقصى). يحفظ setMeasuredDimension(width, height) نتيجة القياس. عند تغيير البيانات، استدع requestLayout() لإعادة القياس. تتطلب ViewGroup المتداخلة إعادة تعريف onLayout لترتيب العناصر الفرعية.
| المنصة | طريقة القياس | الاستدعاء عند التغيير | الإرجاع |
|---|---|---|---|
| iOS | sizeThatFits / intrinsicContentSize | setNeedsLayout / invalidateIntrinsicContentSize | CGSize |
| Android | onMeasure / setMeasuredDimension | requestLayout / invalidate | void (يحفظ الحجم) |
| Flutter | computeDryLayout | setState / LayoutBuilder | Size |
computeDryLayout هي طريقة RenderObject لحساب الحجم دون دورة تخطيط كاملة. LayoutBuilder.getSize — بديل للحصول على المساحة المتاحة في طريقة build. في Flutter، يتم تمرير حجم اللوحة إلى paint كـ Size ولا يتطلب قياسًا منفصلاً — يرسم CustomPainter داخل الحدود المُمررة. ومع ذلك، بالنسبة لـ RenderObject المخصصة، تحتاج إلى إعادة تعريف performLayout و computeDryLayout.
الأسئلة الشائعة
drawRect هي طريقة UIView للرسم المخصص عبر Core Graphics. يتم استدعاؤها عند العرض الأول للعرض وبعد كل استدعاء setNeedsDisplay(). استخدم CGContext داخل drawRect للخطوط والأشكال والنص.
أنشئ فئة ترث View، وأعد تعريف onDraw(Canvas canvas) واستخدم Canvas API: drawCircle و drawRect و drawPath. يقوم Paint بتكوين اللون والسمك والنمط. للتحديث، استدع invalidate().
CustomPainter هي فئة Flutter للرسم المخصص على Canvas. قم بتنفيذ طريقتي paint(Canvas, Size) و shouldRepaint. يتم وضعه عبر عنصر واجهة CustomPaint. لا يحتوي Flutter على drawRect — كل الرسم يتم عبر CustomPainter.
على iOS استخدم sizeThatFits و intrinsicContentSize. على Android — onMeasure مع MeasureSpec. في Flutter، يتم تمرير الحجم إلى paint(Canvas, Size) أو حسابه عبر computeDryLayout. كل منصة تستخدم طريقتها الخاصة في القياس.
العروض المخصصة ضرورية للرسومات الفريدة والرسوم المتحركة والمخططات والعناصر التي لا يمكن تنفيذها باستخدام UIKit أو View القياسية. للأنماط البسيطة، يكفي تكوين المكونات القياسية — لا تُعقد البنية بدون داع.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.