dispatchDraw(): چیست، مکانیسم فراخوانی و تفاوت با onDraw

نویسنده: IT Sectr منتشر شده: 2026-07-22 زمان مطالعه: 9 دقیقه

dispatchDraw() — متدی از کلاس ViewGroup است که مسئول ترسیم بازگشتی همه Viewهای فرزند در سلسله‌مراتب Android است. طبق Android Developers Documentation (2026)، dispatchDraw به‌طور خودکار پس از onDraw در متد draw() View اصلی فراخوانی می‌شود و تمام عناصر فرزند را پیمایش کرده، draw() خودشان را فراخوانی می‌کند. توسعه‌دهندگان dispatchDraw را در ViewGroupهای سفارشی برای افزودن افکت‌ها، قرار دادن گرافیک روی فرزندان یا تغییر ترتیب ترسیم بازنویسی می‌کنند.

نکات اصلی

  • dispatchDraw(Canvas canvas) — متد protected ViewGroup که پس از onDraw برای ترسیم Viewهای فرزند فراخوانی می‌شود
  • 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 برای ترسیم همه 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 ساده‌تر است.

خط لوله ترسیم: onDraw, dispatchDraw, onDrawForeground

متد 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 و بازسازی حافظه نهان منجر شود که بر عملکرد تأثیر می‌گذارد.

متدترتیب فراخوانیهدف
drawBackground1ترسیم پس‌زمینه (background drawable)
onDraw2ترسیم محتوای خود View
dispatchDraw3ترسیم همه Viewهای فرزند (فقط برای ViewGroup)
onDrawForeground4ترسیم foreground، scrollbar و افکت ripple

تفاوت dispatchDraw با onDraw

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 با کانال آلفا) را روی فرزندان اضافه کنید.

مثال بازنویسی dispatchDraw در Kotlin

OverlayViewGroup — یک ViewGroup سفارشی ایجاد می‌کنیم که یک لایه نیمه‌شفاف با برچسب متنی روی همه Viewهای فرزند اضافه می‌کند. dispatchDraw ابتدا super را فراخوانی می‌کند (ترسیم همه فرزندان)، سپس مستطیل overlay و متن را ترسیم می‌کند. 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-order متحرک از طریق 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 در سناریوهایی استفاده می‌شود که نیاز به تأثیر بر فرآیند ترسیم 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 را مستقیماً بدون سیستم فراخوانی کرد؟

خیر، dispatchDraw — متد protected است که توسط سیستم Android در داخل متد public draw() فراخوانی می‌شود. فراخوانی مستقیم dispatchDraw معنی ندارد، زیرا اقدامات مقدماتی (ذخیره Canvas، ترسیم پس‌زمینه و foreground) را انجام نمی‌دهد. به جای آن از View.draw(Canvas) برای ترسیم برنامه‌ریزی‌شده در یک Canvas دلخواه استفاده کنید.

هنگام فراخوانی super.dispatchDraw پس از ترسیم سفارشی چه اتفاقی می‌افتد؟

اگر super.dispatchDraw(canvas) را پس از عملیات ترسیم خود فراخوانی کنید، Viewهای فرزند روی گرافیک سفارشی ترسیم خواهند شد. این ترتیب لایه‌ها را تغییر می‌دهد: ابتدا لایه سفارشی، سپس فرزندان ترسیم می‌شوند. اگر وضعیت معکوس مورد نیاز است (گرافیک روی فرزندان)، ابتدا super.dispatchDraw و سپس عملیات خود را فراخوانی کنید.

dispatchDraw چگونه بر عملکرد تأثیر می‌گذارد؟

dispatchDraw برای هر View فرزند draw() را فراخوانی می‌کند، زمان کل متناسب با تعداد فرزندان است. در hardware acceleration، افزودن عملیات Canvas در dispatchDraw می‌تواند به بی‌اعتبار شدن DisplayList و بازسازی حافظه نهان منجر شود. برای 5–10 فرزند تأثیر حداقل است، برای 50+ فرزند توصیه می‌شود overlayهای پیچیده را کش کنید.

چرا dispatchDraw برای View فراخوانی نمی‌شود؟

View (نه ViewGroup) عناصر فرزند ندارد، بنابراین dispatchDraw کار مفیدی انجام نمی‌دهد. با این حال، متد در کلاس پایه View برای چندریختی وجود دارد: کد draw() برای هر View dispatchDraw را فراخوانی می‌کند، اما پیاده‌سازی View حاوی منطق ترسیم فرزندان نیست. فقط ViewGroup dispatchDraw را بازنویسی می‌کند.

آیا می‌توان Canvas را در dispatchDraw تغییر داد؟

بله، dispatchDraw Canvas را دریافت می‌کند و می‌توان آن را قبل از فراخوانی super.dispatchDraw تبدیل کرد (translate, rotate, scale). این برای انیمیشن کل مجموعه فرزندان به عنوان یک کل واحد استفاده می‌شود. پس از اتمام super.dispatchDraw توصیه می‌شود Canvas را به حالت اولیه بازگردانید.

خلاصه

  • dispatchDraw(Canvas) — متد ViewGroup برای ترسیم بازگشتی همه Viewهای فرزند پس از 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 از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید