dispatchDraw() — متدی از کلاس ViewGroup است که مسئول ترسیم بازگشتی همه Viewهای فرزند در سلسلهمراتب Android است. طبق Android Developers Documentation (2026)، dispatchDraw بهطور خودکار پس از onDraw در متد draw() View اصلی فراخوانی میشود و تمام عناصر فرزند را پیمایش کرده، draw() خودشان را فراخوانی میکند. توسعهدهندگان dispatchDraw را در ViewGroupهای سفارشی برای افزودن افکتها، قرار دادن گرافیک روی فرزندان یا تغییر ترتیب ترسیم بازنویسی میکنند.
نکات اصلی
dispatchDraw(Canvas canvas) — متد protected کلاس ViewGroup است که سیستم Android برای ترسیم همه Viewهای فرزند کانتینر جاری فراخوانی میکند. dispatchDraw بخشی از خط لوله استاندارد draw است: ابتدا onDraw (ترسیم محتوای خود View)، سپس dispatchDraw (ترسیم بازگشتی فرزندان)، سپس onDrawForeground (ترسیم foreground و scrollbars) اجرا میشود. این متد برای فراخوانی مستقیم از کد برنامه در نظر گرفته نشده است.
پیادهسازی استاندارد dispatchDraw در ViewGroup همه Viewهای فرزند را پیمایش میکند، visibility آنها را بررسی کرده و برای هر کدام draw(Canvas) را فراخوانی میکند. ترتیب پیمایش مطابق با ایندکس فرزندان است (از 0 تا childCount - 1). اگر Viewهای فرزند روی هم بیفتند، آخرینها در لیست روی اولینها ترسیم میشوند. با تغییر ترتیب در dispatchDraw میتوان Z-order ترسیم را تغییر داد.
برای View (نه ViewGroup) متد dispatchDraw خالی است — View معمولی عناصر فرزند ندارد، بنابراین چیزی برای ترسیم وجود ندارد. این متد در کلاس پایه View وجود دارد، اما فقط برای نمونههای ViewGroup فراخوانی میشود. توسعهدهنده میتواند وجود عناصر فرزند را از طریق رفتار پیشفرض dispatchDraw بررسی کند، اما معمولاً بررسی instanceof ViewGroup سادهتر است.
متد draw(Canvas) کلاس View یک خط لوله ترسیم سه مرحلهای را سازماندهی میکند. مرحله اول — فراخوانی onDraw(canvas) که در آن View محتوای خود را ترسیم میکند. مرحله دوم — فراخوانی dispatchDraw(canvas) که ترسیم Viewهای فرزند را آغاز میکند. مرحله سوم — onDrawForeground(canvas) که مسئول لایه foreground، scrollbars و افکتهای ripple است. این توالی ترتیب صحیح قرارگیری لایههای گرافیکی را تضمین میکند.
onDraw همیشه قبل از dispatchDraw اجرا میشود. این بدان معناست که محتوای View والد در زیر Viewهای فرزند نمایش داده میشود. اگر نیاز به ترسیم چیزی روی فرزندان دارید، این کار در onDrawForeground یا در dispatchDraw بازنویسیشده با فراخوانی super.dispatchDraw(canvas) و ترسیم بعدی روی آن انجام میشود. پسزمینه (background) حتی قبل از onDraw — در drawBackground(canvas) داخل متد draw() ترسیم میشود.
در شتاب سختافزاری (hardware acceleration) خط لوله draw از طریق GPU کار میکند. View در DisplayList ترسیم میشود — لیستی از دستورات ترسیم که ذخیره و مجدداً استفاده میشود. dispatchDraw در حالت شتاب سختافزاری DisplayList Viewهای فرزند را به DisplayList کلی صحنه اضافه میکند. تغییر dispatchDraw ممکن است به بیاعتبار شدن DisplayList و بازسازی حافظه نهان منجر شود که بر عملکرد تأثیر میگذارد.
| متد | ترتیب فراخوانی | هدف |
|---|---|---|
| drawBackground | 1 | ترسیم پسزمینه (background drawable) |
| onDraw | 2 | ترسیم محتوای خود View |
| dispatchDraw | 3 | ترسیم همه Viewهای فرزند (فقط برای ViewGroup) |
| onDrawForeground | 4 | ترسیم foreground، scrollbar و افکت ripple |
onDraw مسئول ترسیم محتوای خود View — اشکال، متن، تصاویری که به این عنصر خاص تعلق دارند — است. dispatchDraw مسئول ترسیم Viewهای فرزند — همه عناصری که در داخل ViewGroup قرار دارند — است. اگر ViewGroup dispatchDraw را بازنویسی نمیکند، از پیادهسازی پیشفرض ViewGroup استفاده میشود که به سادگی به صورت بازگشتی childCount را پیمایش کرده و برای هر فرزند draw را فراخوانی میکند.
برای ViewGroup که محتوای خود را ترسیم نمیکند (مثلاً FrameLayout, LinearLayout)، onDraw را میتوان با تنظیم setWillNotDraw(true) بهینهسازی کرد. در این صورت onDraw هرگز فراخوانی نمیشود که باعث صرفهجویی در منابع میشود. dispatchDraw در این حالت به کار خود ادامه داده و برای عناصر فرزند draw را فراخوانی میکند. همه ViewGroupهای استاندارد (LinearLayout, RelativeLayout, ConstraintLayout) از setWillNotDraw(true) استفاده میکنند.
اگر dispatchDraw بدون فراخوانی super.dispatchDraw(canvas) بازنویسی شود، Viewهای فرزند ترسیم نخواهند شد.这可能 برای پنهان کردن موقت همه فرزندان مفید باشد، اما در بیشتر موارد باعث ایجاد خطا میشود. روش توصیهشده: super.dispatchDraw(canvas) را در ابتدای متد بازنویسیشده فراخوانی کنید، سپس گرافیک خود (مثلاً overlay با کانال آلفا) را روی فرزندان اضافه کنید.
OverlayViewGroup — یک ViewGroup سفارشی ایجاد میکنیم که یک لایه نیمهشفاف با برچسب متنی روی همه Viewهای فرزند اضافه میکند. dispatchDraw ابتدا super را فراخوانی میکند (ترسیم همه فرزندان)، سپس مستطیل overlay و متن را ترسیم میکند. 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 در سناریوهایی استفاده میشود که نیاز به تأثیر بر فرآیند ترسیم Viewهای فرزند بدون تأثیر بر محتوای خود ViewGroup دارید. سناریوهای اصلی: اعمال یک افکت مشترک (overlay, shadow, gradient) روی همه فرزندان; تغییر ترتیب Z-order برای ایجاد افکت عمق; انیمیشن ظاهر یا ناپدید شدن عناصر فرزند از طریق تبدیلهای Canvas.
اگر نیاز به قرار دادن گرافیک در زیر عناصر فرزند دارید (افکت پسزمینه)، باید از onDraw استفاده کنید — dispatchDraw بعد از آن فراخوانی میشود. اگر گرافیک باید روی فرزندان باشد — از dispatchDraw با فراخوانی super و سپس Canvas.draw استفاده کنید. اگر تغییر سراسری رنگ یا شفافیت مورد نیاز است، استفاده از canvas.saveLayerAlpha() در dispatchDraw که ترسیم فرزندان را احاطه میکند، راحت است.
توصیه نمیشود از dispatchDraw برای: 1) ترسیم انیمیشن پیچیده در زمان واقعی (از invalidate و onDraw استفاده کنید); 2) ایجاد اسکرینشات از ViewGroup (از buildDrawingCache() یا View.draw(Canvas) استفاده کنید); 3) تغییر Viewهای فرزند (مختصات، اندازهها — این وظیفه onLayout است، نه dispatchDraw). dispatchDraw — فقط برای اعمال افکتهای بصری.
سوالات متداول
خیر، dispatchDraw — متد protected است که توسط سیستم Android در داخل متد public draw() فراخوانی میشود. فراخوانی مستقیم dispatchDraw معنی ندارد، زیرا اقدامات مقدماتی (ذخیره Canvas، ترسیم پسزمینه و foreground) را انجام نمیدهد. به جای آن از View.draw(Canvas) برای ترسیم برنامهریزیشده در یک Canvas دلخواه استفاده کنید.
اگر super.dispatchDraw(canvas) را پس از عملیات ترسیم خود فراخوانی کنید، Viewهای فرزند روی گرافیک سفارشی ترسیم خواهند شد. این ترتیب لایهها را تغییر میدهد: ابتدا لایه سفارشی، سپس فرزندان ترسیم میشوند. اگر وضعیت معکوس مورد نیاز است (گرافیک روی فرزندان)، ابتدا super.dispatchDraw و سپس عملیات خود را فراخوانی کنید.
dispatchDraw برای هر View فرزند draw() را فراخوانی میکند، زمان کل متناسب با تعداد فرزندان است. در hardware acceleration، افزودن عملیات Canvas در dispatchDraw میتواند به بیاعتبار شدن DisplayList و بازسازی حافظه نهان منجر شود. برای 5–10 فرزند تأثیر حداقل است، برای 50+ فرزند توصیه میشود overlayهای پیچیده را کش کنید.
View (نه ViewGroup) عناصر فرزند ندارد، بنابراین dispatchDraw کار مفیدی انجام نمیدهد. با این حال، متد در کلاس پایه View برای چندریختی وجود دارد: کد draw() برای هر View dispatchDraw را فراخوانی میکند، اما پیادهسازی View حاوی منطق ترسیم فرزندان نیست. فقط ViewGroup dispatchDraw را بازنویسی میکند.
بله، dispatchDraw Canvas را دریافت میکند و میتوان آن را قبل از فراخوانی super.dispatchDraw تبدیل کرد (translate, rotate, scale). این برای انیمیشن کل مجموعه فرزندان به عنوان یک کل واحد استفاده میشود. پس از اتمام super.dispatchDraw توصیه میشود Canvas را به حالت اولیه بازگردانید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید