dispatchDraw() هو أسلوب من فئة ViewGroup المسؤول عن الرسم التكراري لجميع طرق العرض التابعة في هرمية Android. وفقًا لوثائق مطوري Android (2026)، يتم استدعاء dispatchDraw تلقائيًا بعد onDraw في أسلوب draw() لعرض View الرئيسي ويمر عبر جميع العناصر التابعة، مستدعيًا draw() الخاص بها. يقوم المطورون بإعادة تعريف dispatchDraw في ViewGroups المخصصة لإضافة تأثيرات، أو تراكب الرسومات فوق العناصر التابعة، أو تغيير ترتيب الرسم.
الخلاصة
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 أبسط.
ينظم 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 ويسبب إعادة بناء الذاكرة المخبأة، مما يؤثر على الأداء.
| الأسلوب | ترتيب الاستدعاء | الغرض |
|---|---|---|
| drawBackground | 1 | رسم الخلفية (background drawable) |
| onDraw | 2 | رسم محتوى View نفسه |
| dispatchDraw | 3 | رسم جميع طرق العرض التابعة (ViewGroup فقط) |
| onDrawForeground | 4 | رسم الطبقة الأمامية وشريط التمرير وتأثير ripple |
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) في بداية الأسلوب المعاد تعريفه، ثم إضافة الرسومات الخاصة بك (مثل تراكب بقناة ألفا) فوق التابعين.
لننشئ OverlayViewGroup — ViewGroup مخصص يضيف طبقة شبه شفافة مع تسمية نصية فوق جميع طرق العرض التابعة. dispatchDraw أولاً يستدعي super (رسم جميع التابعين)، ثم يرسم مستطيل التراكب والنص. يتم إنشاء Paint في المنشئ لتجنب تخصيص الذاكرة في حلقة draw.
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))
}
}
المثال التالي يوضح تغيير ترتيب رسم العناصر التابعة. CircularRevealLayout يعيد تعريف dispatchDraw ويرسم التابعين بترتيب عكسي، مما يخلق تأثير Z-order مقلوب. للرسوم المتحركة، يتم إضافة إزاحة دورية لمؤشر الرسم بناءً على قيمة الإزاحة المتحركة offset.
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 يُستخدم في السيناريوهات التي تحتاج فيها إلى التأثير على عملية رسم طرق العرض التابعة دون التأثير على محتوى 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 هو أسلوب protected يستدعيه نظام Android داخل الأسلوب العام draw(). الاستدعاء المباشر لـ dispatchDraw غير مفيد لأنه لا ينفذ إجراءات تحضيرية (حفظ Canvas، رسم الخلفية والطبقة الأمامية). بدلاً من ذلك، استخدم View.draw(Canvas) للرسم البرمجي على Canvas عشوائي.
إذا استدعيت super.dispatchDraw(canvas) بعد عمليات الرسم الخاصة بك، فسيتم رسم طرق العرض التابعة فوق الرسومات المخصصة. هذا يغير ترتيب الطبقات: تُرسم الطبقة المخصصة أولاً، ثم التابعين. إذا كنت بحاجة إلى الوضع المعاكس (رسومات فوق التابعين)، استدعِ super.dispatchDraw أولاً، ثم عملياتك الخاصة.
dispatchDraw يستدعي draw() لكل View تابع، والوقت الإجمالي يتناسب مع عدد التابعين. مع تسريع الأجهزة، إضافة عمليات Canvas في dispatchDraw قد يبطل DisplayList ويسبب إعادة بناء الذاكرة المخبأة. لـ 5–10 تابعين يكون التأثير ضئيلاً، ولـ 50+ تابعًا يُوصى بتخزين التراكبات المعقدة مؤقتًا.
View (وليس ViewGroup) لا يحتوي على عناصر تابعة، لذلك لا يقوم dispatchDraw بأي عمل مفيد. ومع ذلك، الأسلوب موجود في الفئة الأساسية View لتعدد الأشكال: الكود في draw() يستدعي dispatchDraw لأي View، لكن تنفيذ View لا يحتوي على منطق رسم التابعين. فقط ViewGroup يعيد تعريف dispatchDraw.
نعم، dispatchDraw يقبل Canvas ويمكن تحويله قبل استدعاء super.dispatchDraw (translate, rotate, scale). يُستخدم هذا لتحريك مجموعة التابعين بأكملها كوحدة واحدة. بعد اكتمال super.dispatchDraw، يُوصى باستعادة Canvas إلى حالته الأصلية.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.