onDraw() — چیست، مکانیزم فراخوانی و بازنویسی متد

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

onDraw() — متد کلیدی کلاس View در Android SDK است که مسئول ترسیم محتوای نمایش است. سیستم onDraw را در هر به‌روزرسانی ظاهر View فراخوانی می‌کند — در هنگام رندر اولیه، پس از invalidate() یا هنگام تغییر اندازه‌ها. طبق Android Developers Documentation (2026)، بازنویسی onDraw با استفاده از Canvas API روش اصلی ایجاد گرافیک سفارشی در برنامه‌های Android است.

نکات اصلی

  • onDraw(Canvas) — متد View که توسط سیستم برای ترسیم محتوا روی Canvas فراخوانی می‌شود
  • invalidate() — درخواست ترسیم مجدد View که منجر به فراخوانی onDraw در چرخه فریم بعدی می‌شود
  • Canvas API متدهای drawCircle، drawBitmap، drawText، drawPath و دیگر روش‌های ترسیم را فراهم می‌کند
  • سیستم Android ترسیم را در نخ UI انجام می‌دهد، بنابراین onDraw باید سبک و سریع باشد
  • Hardware acceleration از Android 3.0 (API 11) به بعد به طور خودکار ترسیم را از طریق GPU加速 می‌کند

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 تغییر می‌کند (layout جدید); 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 است. قبل از فراخوانی متدهای draw، توسعه‌دهنده می‌تواند سیستم مختصات را از طریق canvas.translate(dx, dy)، canvas.rotate(degrees, px, py)، canvas.scale(sx, sy, px, py) تغییر دهد. تمام عملیات ترسیم بعدی در سیستم تبدیل‌شده انجام خواهد شد.

عملکرد onDraw

onDraw در نخ اصلی UI اجرا می‌شود و مدت زمان آن مستقیماً بر نرخ فریم (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 (تبدیل و clip) را در پشته ذخیره می‌کند. canvas.restore() آخرین وضعیت ذخیره شده را بازیابی می‌کند. این برای تبدیل‌های موقت ضروری است: وضعیت را ذخیره می‌کنید، translate/rotate را اعمال می‌کنید، ترسیم می‌کنید، سپس بازیابی می‌کنید. save و restore سبک هستند و در استفاده معقول (کمتر از 10–15 سطح تودرتو) بر عملکرد تأثیر نمی‌گذارند.

invalidate و postInvalidate

invalidate() — متد View است که به سیستم اطلاع می‌دهد محتوای View منقضی شده و نیاز به ترسیم مجدد دارد. فراخوانی invalidate یک چرخه draw جدید را آغاز می‌کند که در آن onDraw فراخوانی خواهد شد. invalidate باید فقط از نخ UI (main thread) فراخوانی شود. برای فراخوانی از نخ پس‌زمینه از postInvalidate استفاده می‌شود.

postInvalidate() — نسخه thread-safe از invalidate است که درخواست ترسیم مجدد را از طریق Handler به صف نخ اصلی ارسال می‌کند. در وظایف پس‌زمینه زمانی که نیاز به به‌روزرسانی View پس از اتمام بارگذاری داده‌ها یا محاسبات است استفاده می‌شود. برای همگام‌سازی با UI با 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("سلام از 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() (از نخ UI) یا postInvalidate() (از نخ پس‌زمینه) استفاده کنید. سیستم خود تصمیم می‌گیرد چه زمانی onDraw را فراخوانی کند و با ترکیب invalidateهای متعدد در یک مرحله، رندر را بهینه می‌کند.

چرا onDraw پس از invalidate فراخوانی نمی‌شود؟

دلایل: View مخفی است (setVisibility(GONE) یا INVISIBLE); View به سلسله‌مراتب Window اضافه نشده است; اندازه View برابر ۰ است (width یا height = 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 منتقل می‌شود متعلق به سیستم است و فقط در نخ UI در داخل متد onDraw قابل استفاده است.

خلاصه

  • onDraw(Canvas) — متد کلیدی View برای ترسیم سفارشی با استفاده از Canvas API در Android
  • چرخه ترسیم از فازهای measure، layout و draw تشکیل شده است — onDraw در فاز draw فراخوانی می‌شود
  • Canvas API شامل متدهای drawCircle، drawRect، drawPath، drawBitmap، drawText و دیگران است
  • invalidate() از نخ UI و postInvalidate() از نخ پس‌زمینه درخواست ترسیم مجدد می‌دهد
  • ترسیم مجدد جزئی از طریق invalidate(Rect) رندر را با به‌روزرسانی فقط ناحیه dirty بهینه می‌کند
  • عملکرد onDraw حیاتی است: ایجاد اشیاء در داخل متد باعث GC و افت فریم می‌شود
  • شتاب سخت‌افزاری (از API 11) عملیات Canvas را از طریق GPU به طور خودکار سریع‌تر می‌کند

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

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

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