onDraw() — ما هو، آلية الاستدعاء وإعادة تعريف الملخص

المؤلف: IT Sectr نُشر: 2026-07-22 وقت القراءة: 10 دق

onDraw() هو ملخص رئيسي من فئة View في Android SDK المسؤول عن رسم محتوى العرض. يقوم النظام باستدعاء onDraw في كل تحديث لمظهر View — أثناء العرض الأولي، بعد invalidate() أو عند تغير الأبعاد. وفقًا لوثائق Android Developers (2026)، فإن إعادة تعريف onDraw باستخدام Canvas API هي الطريقة الرئيسية لإنشاء رسومات مخصصة في تطبيقات Android.

النقاط الرئيسية

  • onDraw(Canvas) — ملخص View يستدعيه النظام لرسم المحتوى على Canvas
  • invalidate() — طلب إعادة رسم View، مما يؤدي إلى استدعاء onDraw في دورة الإطار التالية
  • Canvas API توفر الملخصات drawCircle، drawBitmap، drawText، drawPath وغيرها للرسم
  • يقوم نظام Android بالرسم على خط واجهة المستخدم، لذا يجب أن يكون onDraw خفيفًا وسريعًا
  • Hardware acceleration تسرع الرسم تلقائيًا عبر GPU، بدءًا من Android 3.0 (API 11)

ما هو onDraw()؟

onDraw(Canvas canvas) هو ملخص protected من فئة android.view.View، يستدعيه نظام Android عندما يحتاج View إلى الرسم. يقوم المطور بإعادة تعريف هذا الملخص لتنفيذ رسم مخصص: أشكال، نصوص، صور، تدريجات، رسوم متحركة. معلمة Canvas هي سطح رسومي تجري عليه جميع عمليات الرسم.

توقيع الملخص بسيط: override fun onDraw(canvas: Canvas). داخل هذا الملخص، لا يجب إنشاء كائنات جديدة (لتجنب عبء GC)، أو تنفيذ عمليات طويلة أو العمل مع الشبكة. تشمل View أيضًا الملخص onDraw في دورة حياتها بين onLayout (حساب المواقع) و dispatchDraw (رسم العناصر التابعة).

عند الوراثة من View، يكون إعادة تعريف onDraw إجباريًا لعرض أي محتوى — وإلا ستكون View فارغة. عند الوراثة من فئات فرعية موجودة (TextView، ImageView، Button)، فإعادة تعريف onDraw اختيارية وتستخدم لإضافة رسومات إضافية فوق المحتوى القياسي.

دورة رسم View

Android يقوم برسم View بتسلسل محدد بدقة يتم استدعاؤه من الخط الرئيسي (UI thread). تتكون الدورة الكاملة من ثلاث مراحل: measure (قياس الأبعاد)، layout (حساب المواقع) و draw (العرض). يعد onDraw جزءًا من مرحلة draw ويتم استدعاؤه بعد تحديد أبعاد وموقع View.

مرحلة draw: dispatchDraw و onDraw

draw(Canvas canvas) هو ملخص عام من View يستدعي onDraw و dispatchDraw. الملخص draw ليس مصممًا للإعادة تعريفه — بدلًا من ذلك، يتم إعادة تعريف onDraw. بعد تنفيذ onDraw لـ View الحالية، يتم استدعاء dispatchDraw، الذي يستدعي بشكل عودي draw لجميع View التابعة (إذا كانت View الحالية هي حاوية ViewGroup).

متى يستدعي النظام onDraw

onDraw يتم استدعاؤه في الظروف التالية: 1) تصبح View مرئية لأول مرة (بعد setVisibility(VISIBLE))؛ 2) يستدعي التطبيق invalidate() لهذه View أو والدها؛ 3) يتغير حجم View (تخطيط جديد)؛ 4) تتغير حركة مرتبطة بـ View (عبر ValueAnimator أو ObjectAnimator)؛ 5) تتغير حالة View (pressed، focused، enabled).

التسارع الجهازي

منذ Android 3.0 (API 11)، تتم معظم عمليات Canvas بتسارع جهازي عبر GPU. يمكن للمطور التحقق مما إذا كان التسارع مفعلًا من خلال canvas.isHardwareAccelerated(). مع التسارع الجهازي، بعض عمليات Canvas غير مدعومة (على سبيل المثال، clipPath بمنطقة غير مستطيلة) وقد تتم تجاهلها أو تتسبب في أخطاء.

Canvas API للرسومات المخصصة

Canvas توفر مجموعة غنية من ملخصات الرسم. تعمل جميع الملخصات في نظام الإحداثيات الحالي، والذي يمكن تغييره من خلال التحويلات (translate، rotate، scale، skew). يقبل كل ملخص كائن Paint يحدد اللون، والأسلوب، وسمك الخط، والتأثيرات والتنعيم.

ملخص Canvasالغرضمعلمات Paint الرئيسية
drawCircleرسم دائرةcenterX, centerY, radius, paint
drawRectرسم مستطيلleft, top, right, bottom, paint
drawLineرسم خطstartX, startY, endX, endY, paint
drawPathرسم مسار اعتباطيpath, paint
drawBitmapرسم صورة نقطيةbitmap, srcRect, dstRect, paint
drawTextرسم نصtext, x, y, paint
drawArcرسم قوس أو قطاعovalRect, startAngle, sweepAngle, useCenter, paint
drawOvalرسم بيضاويovalRect, paint

كائن Paint

Paint يحدد أسلوب الرسم: اللون (setColor)، سمك الخط (setStrokeWidth)، نمط التعبئة (setStyle — FILL، STROKE، FILL_AND_STROKE)، التنعيم (setAntiAlias)، التأثيرات (setShadowLayer، setMaskFilter) والتدريجات (setShader). يوصى بإنشاء وتكوين Paint مرة واحدة في منشئ View، وليس داخل onDraw، لتجنب إنشاء الكائنات في حلقة الرسم.

الإحداثيات والتحويلات

Canvas تستخدم نظام إحداثيات حيث يتجه محور X لليمين ومحور Y للأسفل. نقطة الأصل (0, 0) هي الزاوية العليا اليسرى لـ View. قبل استدعاء ملخصات الرسم، يمكن للمطور تغيير نظام الإحداثيات من خلال canvas.translate(dx, dy)، canvas.rotate(degrees, px, py)، canvas.scale(sx, sy, px, py). ستتم جميع عمليات الرسم اللاحقة في النظام المحول.

أداء onDraw

onDraw يتم تنفيذه على خط واجهة المستخدم الرئيسي، وتؤثر مدته مباشرة على معدل الإطارات (fps). للحفاظ على 60 fps، يجب أن يكتمل onDraw خلال 16 ميلي ثانية. يؤدي أي بطء إلى تسقط الإطارات (jank)، والذي يظهر بصريًا كتقطع في الحركة أو تأخر في التمرير.

قواعد أداء onDraw

القواعد الرئيسية لتحسين onDraw تشمل: 1) عدم إنشاء كائنات داخل onDraw — جميع كائنات Paint، Path، Rect يجب أن تكون حقولًا للفئة؛ 2) عدم تخصيص ذاكرة (new) — هذا يسبب تجمع القمامة (GC) وتسقط الإطارات؛ 3) تخزين العمليات المعقدة مؤقتًا في Bitmap من خلال Bitmap.createBitmap ورسم Bitmap المحضر؛ 4) عدم تنفيذ عمليات إدخال/إخراج أو تحميل الموارد.

استخدام clipRect للتحسين

clipRect يحد منطقة الرسم بمستطيل محدد. يستخدم لمنع Canvas من إضاعة الوقت في رسم البكسلات خارج المنطقة المرئية. يكون فعالًا خاصة عند التمرير في القوائم الكبيرة أو التكبير: ضبط clipRect على المنطقة المرئية (dirty rect)، وسيرسم Canvas فقط ما هو مرئي.

Layer — الحفظ والاستعادة

canvas.save() يحفظ الحالة الحالية لـ Canvas (التحويل والقص) على مكدسة. canvas.restore() يستعيد آخر حالة محفوظة. هذا ضروري للتحويلات المؤقتة: احفظ الحالة، طبق translate/rotate، ارسم، ثم استعد. save و restore خفيفان ولا يؤثران على الأداء عند استخدامهما بشكل معقول (أقل من 10–15 مستوى تداخل).

invalidate و postInvalidate

invalidate() هو ملخص View يخبر النظام أن محتوى View قديم ويحتاج إلى إعادة الرسم. يبدأ استدعاء invalidate دورة draw جديدة يتم فيها استدعاء onDraw. يجب استدعاء invalidate فقط من خط واجهة المستخدم (الخط الرئيسي). للاستدعاء من خط خلفي، يستخدم postInvalidate.

postInvalidate() هي نسخة آمنة للخيوط من invalidate ترسل طلب إعادة الرسم إلى طابور الخط الرئيسي عبر Handler. تستخدم في مهام الخلفية عندما يحتاج View إلى التحديث بعد اكتمال تحميل البيانات أو الحسابات. يمكن دمجها مع runOnUiThread لتزامن واجهة المستخدم.

للإعادة الرسم الجزئية، استخدم invalidate(Rect dirty) أو invalidate(int l, int t, int r, int b). وهذا يخبر النظام أنه تم تغيير المنطقة المحددة فقط، ليس View بأكملها. يقوم Android بتحسين الرسم بإعادة رسم dirty rect فقط، مما يحسن الأداء بشكل كبير في الرسوم المتحركة ذات التحديثات الجزئية.

أمثلة برمجية بـ Kotlin

دعنا نلق نظرة على أمثلة عملية لإعادة تعريف onDraw لإنشاء View مخصصة في Android باستخدام Kotlin.

View مخصصة — التقدم الدائري

ينشئ هذا المثال مؤشر تقدم دائري مع تعبئة متحركة. يرسم onDraw دائرتين: دائرة خلفية رمادية ودائرة تعبئة ملونة يتم تحديد قوسها بالتقدم الحالي. يضمن استدعاء invalidate في setProgress إعادة الرسم عند تغيير القيمة.

kotlin
class CircularProgressView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    private val bgPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 12f
        color = Color.parseColor("#E0E0E0")
        isAntiAlias = true
    }

    private val progressPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 12f
        color = Color.BLUE
        isAntiAlias = true
        strokeCap = Paint.Cap.ROUND
    }

    var progress: Float = 0f
        set(value) {
            field = value.coerceIn(0f, 1f)
            invalidate()
        }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val size = minOf(width, height) - 24f
        val oval = RectF(12f, 12f,
                          12f + size, 12f + size)

        canvas.drawArc(oval, 270f, 360f, false, bgPaint)
        canvas.drawArc(oval, 270f,
                        progress * 360f,
                        false, progressPaint)
    }
}

View مخصصة — خلفية متدرجة مع نص

يرسم هذا المثال مستطيلاً بتدريج ونصاً فوقه. يتم إنشاء التدريج عبر LinearGradient (Shader)، والنص عبر drawText. يتم إنشاء كلا كائني Paint مرة واحدة في المنشئ، بينما يقوم onDraw فقط بتحديث حجم المستطيل حسب عرض View الحالي.

kotlin
class GradientTextView(context: Context)
    : View(context) {

    private val shaderPaint = Paint().apply {
        isAntiAlias = true
    }

    private val textPaint = Paint().apply {
        color = Color.WHITE
        textSize = 48f
        isAntiAlias = true
        textAlign = Paint.Align.CENTER
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val gradient = LinearGradient(
            0f, 0f, width.toFloat(), 0f,
            Color.parseColor("#FF6B35"),
            Color.parseColor("#FF0040"),
            Shader.TileMode.CLAMP
        )
        shaderPaint.shader = gradient
        canvas.drawRect(0f, 0f,
                        width.toFloat(),
                        height.toFloat(), shaderPaint)

        val xCenter = width / 2f
        val yCenter = height / 2f
        val yOffset = (textPaint.descent() +
                        textPaint.ascent()) / 2f

        canvas.drawText("Hello from Canvas",
                      xCenter, yCenter - yOffset,
                      textPaint)
    }
}

ساعة متحركة باستخدام invalidate

ينشئ هذا المثال ساعة متحركة تقوم بتحديث الرسم كل 1000 ميلي ثانية. يستدعي postDelayed داخل onDraw invalidate بعد ثانية، مما ينشئ حلقة إعادة رسم لا نهائية. تتم رسم عقارب الساعة عبر drawLine بأطوال وسمك مختلفة.

kotlin
class AnalogClockView(context: Context)
    : View(context) {

    private val handPaint = Paint().apply {
        style = Paint.Style.STROKE
        isAntiAlias = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val cx = width / 2f
        val cy = height / 2f
        val radius = minOf(cx, cy) - 20f

        val now = Calendar.getInstance()
        val hours = now.get(Calendar.HOUR) % 12
        val minutes = now.get(Calendar.MINUTE)
        val seconds = now.get(Calendar.SECOND)

        val secondAngle = Math.toRadians(
            seconds * 6f - 90f)
        handPaint.strokeWidth = 2f
        handPaint.color = Color.RED
        canvas.drawLine(cx, cy,
            cx + cos(secondAngle) * radius * 0.8f,
            cy + sin(secondAngle) * radius * 0.8f,
            handPaint)

        postDelayed({ invalidate() }, 1000)
    }
}

الأسئلة الشائعة

هل يمكن استدعاء onDraw مباشرة؟

لا، لا يجب استدعاء onDraw مباشرة. لطلب إعادة الرسم، استخدم invalidate() (من خط واجهة المستخدم) أو postInvalidate() (من خط خلفي). سيقرر النظام متى يستدعي onDraw وسيحسن الرسم بدمج استدعاءات invalidate متعددة في مرة واحدة.

لماذا لا يتم استدعاء onDraw بعد invalidate؟

الأسباب: View مخفية (setVisibility(GONE) أو INVISIBLE)؛ لم تتم إضافة View إلى تسلسل Window؛ حجم View يساوي 0 (العرض أو الارتفاع = 0)؛ يتم استدعاء setWillNotDraw(true) (الافتراضي true لـ ViewGroup). تحقق من كل هذه النقاط عند التصحيح.

ما مدى تكرار استدعاء onDraw؟

عند 60 fps، يمكن استدعاء onDraw حتى 60 مرة في الثانية. عند 120 fps (على الأجهزة ذات شاشات 120 هرتز) — حتى 120 مرة. يجب أن يكتمل كل استدعاء خلال 8–16 ميلي ثانية تقريبًا. أثناء الحركات النشطة عبر ObjectAnimator أو ValueAnimator، يتم تحديد التكرار بعدد إطارات الحركة.

ما الفرق بين onDraw و dispatchDraw؟

onDraw مسؤول عن رسم محتوى View نفسها. dispatchDraw مسؤول عن رسم View التابعة (في ViewGroup). يتم استدعاء dispatchDraw بعد onDraw ويتنقل بشكل عودي عبر جميع العناصر التابعة. لـ View عادية (ليست ViewGroup)، يكون dispatchDraw فارغًا.

هل يمكن استخدام Canvas خارج onDraw؟

نعم، يمكن إنشاء Canvas لـ Bitmap عبر Canvas(bitmap) والرسم عليه في أي خط. يستخدم هذا للعرض المسبق للرسومات المعقدة في الخلفية. ولكن Canvas الممرر إلى onDraw ينتمي إلى النظام ويمكن استخدامه فقط على خط واجهة المستخدم داخل ملخص onDraw.

الملخص

  • onDraw(Canvas) — ملخص View رئيسي للرسم المخصص باستخدام Canvas API في Android
  • دورة الرسم تتكون من مراحل measure، layout و draw — يتم استدعاء onDraw في مرحلة draw
  • Canvas API تشمل الملخصات drawCircle، drawRect، drawPath، drawBitmap، drawText وغيرها
  • invalidate() يطلب إعادة الرسم من خط واجهة المستخدم، postInvalidate() من خط خلفي
  • إعادة الرسم الجزئية عبر invalidate(Rect) تحسن العرض بتحديث المنطقة المتسخة فقط
  • أداء onDraw حاسم: إنشاء كائنات داخل الملخص يسبب GC وتسقط الإطارات
  • التسارع الجهازي (منذ API 11) يسرع عمليات Canvas تلقائيًا عبر GPU

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا