dispatchDraw(): ما هو، آلية الاستدعاء والفرق عن onDraw

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

dispatchDraw() هو أسلوب من فئة ViewGroup المسؤول عن الرسم التكراري لجميع طرق العرض التابعة في هرمية Android. وفقًا لوثائق مطوري Android (2026)، يتم استدعاء dispatchDraw تلقائيًا بعد onDraw في أسلوب draw() لعرض View الرئيسي ويمر عبر جميع العناصر التابعة، مستدعيًا draw() الخاص بها. يقوم المطورون بإعادة تعريف dispatchDraw في ViewGroups المخصصة لإضافة تأثيرات، أو تراكب الرسومات فوق العناصر التابعة، أو تغيير ترتيب الرسم.

الخلاصة

  • dispatchDraw(Canvas canvas) هو أسلوب protected من ViewGroup يُستدعى بعد onDraw لرسم طرق العرض التابعة
  • draw(Canvas canvas) هو أسلوب public final من View يستدعي بالتسلسل onDraw و dispatchDraw و onDrawForeground
  • onDraw يرسم محتوى View نفسه، بينما dispatchDraw يرسم جميع العناصر التابعة
  • setWillNotDraw(true) هو تحسين لـ ViewGroup التي لا ترسم محتوى خاصًا بها، فقط العناصر التابعة
  • إعادة تعريف dispatchDraw تُستخدم لتطبيق تأثيرات (blur, overlay) أو تحريك رسم العناصر التابعة

ما هو dispatchDraw()؟

dispatchDraw(Canvas canvas) هو أسلوب protected من فئة ViewGroup يستدعيه نظام Android لرسم جميع طرق العرض التابعة للحاوية الحالية. dispatchDraw هو جزء من خط أنابيب draw القياسي: أولاً يتم تنفيذ onDraw (رسم محتوى View نفسه)، ثم dispatchDraw (رسم تكراري للتابعين)، ثم onDrawForeground (رسم الطبقة الأمامية وأشرطة التمرير). الأسلوب غير مخصص للاستدعاء المباشر من كود التطبيق.

التنفيذ القياسي dispatchDraw في ViewGroup يمر عبر جميع طرق العرض التابعة، ويتحقق من رؤيتها ويستدعي draw(Canvas) لكل منها. ترتيب الاجتياز يتوافق مع فهارس التابعين (من 0 إلى childCount - 1). إذا كانت طرق العرض التابعة متداخلة، فإن العناصر المتأخرة في القائمة تُرسم فوق العناصر السابقة. تغيير الترتيب في dispatchDraw يسمح بتعديل ترتيب Z للرسم.

بالنسبة View (وليس ViewGroup)، فإن أسلوب dispatchDraw فارغ — العرض العادي لا يحتوي على عناصر تابعة، لذلك لا يوجد شيء لرسمه. الأسلوب موجود في الفئة الأساسية View ولكن يتم استدعاؤه فقط في مثيلات ViewGroup. يمكن للمطور التحقق مما إذا كان العرض يحتوي على عناصر تابعة من خلال السلوك الافتراضي لـ dispatchDraw، لكن غالبًا ما يكون التحقق instanceof ViewGroup أبسط.

خط أنابيب الرسم: onDraw, dispatchDraw, onDrawForeground

ينظم draw(Canvas) الخاص بفئة View خط أنابيب رسم من ثلاث مراحل. المرحلة الأولى هي استدعاء onDraw(canvas)، حيث يرسم View محتواه الخاص. المرحلة الثانية هي استدعاء dispatchDraw(canvas)، الذي يقوم بتشغيل رسم طرق العرض التابعة. المرحلة الثالثة هي onDrawForeground(canvas)، المسؤولة عن الطبقة الأمامية وأشرطة التمرير وتأثيرات ripple. يضمن هذا التسلسل الترتيب الصحيح لطبقات الرسومات.

onDraw يتم تنفيذه دائمًا قبل dispatchDraw. هذا يعني أن محتوى العرض الأبوي يظهر تحت طرق العرض التابعة. إذا كنت بحاجة إلى رسم شيء فوق التابعين، يتم ذلك في onDrawForeground أو في dispatchDraw مُعاد تعريفه باستدعاء super.dispatchDraw(canvas) ثم الرسم فوقه. يتم رسم الخلفية حتى قبل onDraw — في drawBackground(canvas) داخل أسلوب draw().

مع تسريع الأجهزة، يعمل خط أنابيب draw عبر GPU. يتم رسم View في DisplayList — قائمة بأوامر الرسم التي يتم تخزينها مؤقتًا وإعادة استخدامها. dispatchDraw في وضع تسريع الأجهزة يضيف DisplayLists الخاصة بطرق العرض التابعة إلى DisplayList العام للمشهد. تعديل dispatchDraw قد يبطل DisplayList ويسبب إعادة بناء الذاكرة المخبأة، مما يؤثر على الأداء.

الأسلوبترتيب الاستدعاءالغرض
drawBackground1رسم الخلفية (background drawable)
onDraw2رسم محتوى View نفسه
dispatchDraw3رسم جميع طرق العرض التابعة (ViewGroup فقط)
onDrawForeground4رسم الطبقة الأمامية وشريط التمرير وتأثير ripple

الفرق بين dispatchDraw و onDraw

onDraw مسؤول عن رسم محتوى View نفسه — الأشكال والنصوص والصور التي تنتمي إلى هذا العنصر المحدد. dispatchDraw مسؤول عن رسم طرق العرض التابعة — جميع العناصر الموجودة داخل ViewGroup. إذا لم يقم ViewGroup بإعادة تعريف dispatchDraw، فسيتم استخدام التنفيذ من ViewGroup، الذي يمر بشكل تكراري عبر childCount ويستدعي draw لكل تابع.

بالنسبة ViewGroup الذي لا يرسم محتواه الخاص (مثل FrameLayout, LinearLayout)، يمكن تحسين onDraw عن طريق تعيين setWillNotDraw(true). في هذه الحالة، لا يتم استدعاء onDraw أبدًا، مما يوفر الموارد. يستمر dispatchDraw في العمل ويستدعي draw للعناصر التابعة. جميع ViewGroups القياسية (LinearLayout, RelativeLayout, ConstraintLayout) تستخدم setWillNotDraw(true).

إذا قمت بإعادة تعريف dispatchDraw دون استدعاء super.dispatchDraw(canvas)، فلن يتم رسم طرق العرض التابعة. قد يكون هذا مفيدًا للإخفاء المؤقت لجميع التابعين، لكنه في معظم الحالات يسبب أخطاء. الممارسة الموصى بها: استدعاء super.dispatchDraw(canvas) في بداية الأسلوب المعاد تعريفه، ثم إضافة الرسومات الخاصة بك (مثل تراكب بقناة ألفا) فوق التابعين.

مثال على إعادة تعريف dispatchDraw في Kotlin

لننشئ OverlayViewGroup — ViewGroup مخصص يضيف طبقة شبه شفافة مع تسمية نصية فوق جميع طرق العرض التابعة. dispatchDraw أولاً يستدعي super (رسم جميع التابعين)، ثم يرسم مستطيل التراكب والنص. يتم إنشاء Paint في المنشئ لتجنب تخصيص الذاكرة في حلقة draw.

kotlin
class OverlayViewGroup(context: Context)
    : ViewGroup(context) {

    private val overlayPaint = Paint().apply {
        color = Color.parseColor("#66000000")
        style = Paint.Style.FILL
    }

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

    init {
        setWillNotDraw(false)
    }

    override fun dispatchDraw(canvas: Canvas) {
        super.dispatchDraw(canvas)
        canvas.drawRect(0f, 0f,
                        width.toFloat(),
                        height.toFloat(), overlayPaint)
        canvas.drawText("وضع المعاينة",
            width / 2f,
            height / 2f, textPaint)
    }

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        var top = t
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val cw = child.measuredWidth
            val ch = child.measuredHeight
            child.layout(l, top, l + cw, top + ch)
            top += ch
        }
    }

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        val maxWidth = resolveSize(
            getChildAt(0).measuredWidth,
            widthMeasureSpec)
        var totalHeight = 0
        for (i in 0 until childCount) {
            totalHeight += getChildAt(i).measuredHeight
        }
        setMeasuredDimension(maxWidth,
            resolveSize(totalHeight, heightMeasureSpec))
    }
}

ترتيب Z متحرك عبر dispatchDraw

المثال التالي يوضح تغيير ترتيب رسم العناصر التابعة. CircularRevealLayout يعيد تعريف dispatchDraw ويرسم التابعين بترتيب عكسي، مما يخلق تأثير Z-order مقلوب. للرسوم المتحركة، يتم إضافة إزاحة دورية لمؤشر الرسم بناءً على قيمة الإزاحة المتحركة offset.

kotlin
class ReverseOrderLayout(context: Context)
    : ViewGroup(context) {

    private var reverse = false

    override fun dispatchDraw(canvas: Canvas) {
        if (!reverse) {
            super.dispatchDraw(canvas)
            return
        }
        for (i in childCount - 1 downTo 0) {
            val child = getChildAt(i)
            if (child.visibility == GONE) continue
            drawChild(canvas, child, getDrawingTime())
        }
    }

    fun toggleReverse() {
        reverse = !reverse
        invalidate()
    }

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        var left = l
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val cw = child.measuredWidth
            child.layout(left, t,
                left + cw, t + child.measuredHeight)
            left += cw
        }
    }

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        var totalWidth = 0
        for (i in 0 until childCount) {
            totalWidth += getChildAt(i).measuredWidth
        }
        setMeasuredDimension(
            resolveSize(totalWidth, widthMeasureSpec),
            resolveSize(getChildAt(0).measuredHeight,
                heightMeasureSpec))
    }
}

متى تستخدم dispatchDraw في Android

dispatchDraw يُستخدم في السيناريوهات التي تحتاج فيها إلى التأثير على عملية رسم طرق العرض التابعة دون التأثير على محتوى ViewGroup نفسه. السيناريوهات الرئيسية: تطبيق تأثير مشترك (تراكب، ظل، تدرج) فوق جميع التابعين؛ تغيير ترتيب Z لإنشاء تأثيرات العمق؛ تحريك ظهور أو اختفاء العناصر التابعة عبر تحويلات Canvas.

إذا كنت بحاجة إلى تطبيق رسومات أسفل العناصر التابعة (تأثير خلفية)، استخدم onDraw — يتم استدعاء dispatchDraw بعده. إذا كانت الرسومات يجب أن تكون فوق التابعين — استخدم dispatchDraw مع استدعاء super أولاً، ثم Canvas.draw. إذا كنت بحاجة إلى تغيير عام في اللون أو الشفافية، فمن المناسب استخدام canvas.saveLayerAlpha() في dispatchDraw، لتغليف رسم التابعين.

لا يُوصى باستخدام dispatchDraw لـ: 1) رسم رسوم متحركة معقدة في الوقت الفعلي (استخدم invalidate و onDraw)؛ 2) إنشاء لقطات شاشة لـ ViewGroup (استخدم buildDrawingCache() أو View.draw(Canvas))؛ 3) تعديل طرق العرض التابعة (الإحداثيات والأحجام — هذه مهام onLayout، وليس dispatchDraw). dispatchDraw فقط لتطبيق التأثيرات البصرية.

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

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

لا، dispatchDraw هو أسلوب protected يستدعيه نظام Android داخل الأسلوب العام draw(). الاستدعاء المباشر لـ dispatchDraw غير مفيد لأنه لا ينفذ إجراءات تحضيرية (حفظ Canvas، رسم الخلفية والطبقة الأمامية). بدلاً من ذلك، استخدم View.draw(Canvas) للرسم البرمجي على Canvas عشوائي.

ماذا يحدث عند استدعاء super.dispatchDraw بعد الرسم المخصص؟

إذا استدعيت super.dispatchDraw(canvas) بعد عمليات الرسم الخاصة بك، فسيتم رسم طرق العرض التابعة فوق الرسومات المخصصة. هذا يغير ترتيب الطبقات: تُرسم الطبقة المخصصة أولاً، ثم التابعين. إذا كنت بحاجة إلى الوضع المعاكس (رسومات فوق التابعين)، استدعِ super.dispatchDraw أولاً، ثم عملياتك الخاصة.

كيف يؤثر dispatchDraw على الأداء؟

dispatchDraw يستدعي draw() لكل View تابع، والوقت الإجمالي يتناسب مع عدد التابعين. مع تسريع الأجهزة، إضافة عمليات Canvas في dispatchDraw قد يبطل DisplayList ويسبب إعادة بناء الذاكرة المخبأة. لـ 5–10 تابعين يكون التأثير ضئيلاً، ولـ 50+ تابعًا يُوصى بتخزين التراكبات المعقدة مؤقتًا.

لماذا لا يتم استدعاء dispatchDraw لـ View؟

View (وليس ViewGroup) لا يحتوي على عناصر تابعة، لذلك لا يقوم dispatchDraw بأي عمل مفيد. ومع ذلك، الأسلوب موجود في الفئة الأساسية View لتعدد الأشكال: الكود في draw() يستدعي dispatchDraw لأي View، لكن تنفيذ View لا يحتوي على منطق رسم التابعين. فقط ViewGroup يعيد تعريف dispatchDraw.

هل يمكن تعديل Canvas في dispatchDraw؟

نعم، dispatchDraw يقبل Canvas ويمكن تحويله قبل استدعاء super.dispatchDraw (translate, rotate, scale). يُستخدم هذا لتحريك مجموعة التابعين بأكملها كوحدة واحدة. بعد اكتمال super.dispatchDraw، يُوصى باستعادة Canvas إلى حالته الأصلية.

الملخص

  • dispatchDraw(Canvas) هو أسلوب ViewGroup للرسم التكراري لجميع طرق العرض التابعة بعد onDraw
  • خط أنابيب draw: drawBackground ← onDraw ← dispatchDraw ← onDrawForeground
  • onDraw يرسم محتوى View نفسه، dispatchDraw يرسم محتوى العناصر التابعة
  • setWillNotDraw(true) يحسّن ViewGroup بتخطي onDraw، لكن dispatchDraw يستمر في العمل
  • إعادة تعريف dispatchDraw تُستخدم لتطبيق التأثيرات، تغيير Z-order، وتحويلات Canvas للتابعين
  • ترتيب super.dispatchDraw والرسم المخصص يحدد ما يظهر فوق التابعين
  • بالنسبة لـ View عادي (ليس ViewGroup) فإن dispatchDraw فارغ — لا يحتوي على عناصر تابعة

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

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

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

اقرأ أيضًا