onDraw() — متد کلیدی کلاس View در Android SDK است که مسئول ترسیم محتوای نمایش است. سیستم onDraw را در هر بهروزرسانی ظاهر View فراخوانی میکند — در هنگام رندر اولیه، پس از invalidate() یا هنگام تغییر اندازهها. طبق Android Developers Documentation (2026)، بازنویسی onDraw با استفاده از Canvas API روش اصلی ایجاد گرافیک سفارشی در برنامههای Android است.
نکات اصلی
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 اختیاری است و برای افزودن گرافیک اضافی روی محتوای استاندارد استفاده میشود.
Android ترسیم View را در یک توالی دقیقاً مشخص انجام میدهد که از نخ اصلی (UI thread) فراخوانی میشود. چرخه کامل شامل سه فاز است: measure (اندازهگیری ابعاد)، layout (محاسبه موقعیتها) و draw (ترسیم). onDraw بخشی از فاز draw است و پس از تعیین اندازهها و موقعیت View فراخوانی میشود.
draw(Canvas canvas) — متد عمومی View است که onDraw و dispatchDraw را فراخوانی میکند. متد draw برای بازنویسی طراحی نشده است — به جای آن onDraw بازنویسی میشود. پس از اجرای onDraw برای View جاری، dispatchDraw فراخوانی میشود که به صورت بازگشتی draw را برای تمام Viewهای فرزند فراخوانی میکند (اگر View جاری یک کانتینر ViewGroup باشد).
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 مجموعه غنی از متدها را برای ترسیم فراهم میکند. همه متدها در سیستم مختصات جاری کار میکنند که میتواند از طریق تبدیلها (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 سبک ترسیم را تعیین میکند: رنگ (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 در نخ اصلی UI اجرا میشود و مدت زمان آن مستقیماً بر نرخ فریم (fps) تأثیر میگذارد. برای حفظ 60 fps، متد onDraw باید حداکثر در 16 میلیثانیه تکمیل شود. هر کندی منجر به افت فریم (jank) میشود که از نظر بصری به صورت لکنت انیمیشن یا تأخیر در اسکرول درک میشود.
قوانین اصلی بهینهسازی onDraw شامل: 1) اشیاء را در داخل onDraw ایجاد نکنید — همه Paint، Path، Rect باید فیلدهای کلاس باشند; 2) حافظه تخصیص ندهید (new) — این کار جمعآوری زباله (GC) و افت فریم را به همراه دارد; 3) عملیات پیچیده را در Bitmap از طریق Bitmap.createBitmap کش کنید و Bitmap آماده را ترسیم کنید; 4) عملیات ورودی-خروجی یا بارگذاری منابع را انجام ندهید.
clipRect ناحیه ترسیم را به یک مستطیل مشخص محدود میکند. برای این استفاده میشود که Canvas وقت خود را برای رندر پیکسلهای خارج از ناحیه قابل مشاهده تلف نکند. به ویژه در اسکرول لیستهای بزرگ یا بزرگنمایی مؤثر است: clipRect را روی ناحیه قابل مشاهده (dirty rect) تنظیم کنید و Canvas فقط آنچه را که قابل مشاهده است ترسیم میکند.
canvas.save() وضعیت جاری Canvas (تبدیل و clip) را در پشته ذخیره میکند. canvas.restore() آخرین وضعیت ذخیره شده را بازیابی میکند. این برای تبدیلهای موقت ضروری است: وضعیت را ذخیره میکنید، translate/rotate را اعمال میکنید، ترسیم میکنید، سپس بازیابی میکنید. save و restore سبک هستند و در استفاده معقول (کمتر از 10–15 سطح تودرتو) بر عملکرد تأثیر نمیگذارند.
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، رندر را بهینه میکند که عملکرد را در انیمیشنهای با بهروزرسانی جزئی به طور قابل توجهی افزایش میدهد.
بیایید نمونههای عملی بازنویسی onDraw برای ایجاد Viewهای سفارشی در Android با Kotlin را بررسی کنیم.
مثال یک نشانگر پیشرفت دایرهای با پر شدن انیمیشنی ایجاد میکند. onDraw دو دایره میکشد: زمینه خاکستری و پررنگ رنگی که قوس آن توسط پیشرفت جاری تعیین میشود. فراخوانی invalidate در setProgress ترسیم مجدد را هنگام تغییر مقدار تضمین میکند.
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)
}
}
مثال یک مستطیل با گرادینت و متن روی آن ترسیم میکند. گرادینت از طریق LinearGradient (Shader) و متن از طریق drawText ایجاد میشود. هر دو Paint یک بار در سازنده ایجاد میشوند و در onDraw فقط اندازه مستطیل بسته به عرض جاری View تغییر میکند.
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)
}
}
مثال یک ساعت انیمیشنی ایجاد میکند که ترسیم را هر 1000 میلیثانیه بهروزرسانی میکند. postDelayed در onDraw بعد از یک ثانیه invalidate را فراخوانی میکند و یک چرخه بینهایت ترسیم مجدد ایجاد میکند. عقربههای ساعت از طریق drawLine با طول و ضخامت مختلف ترسیم میشوند.
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 را مستقیماً فراخوانی کرد. برای درخواست ترسیم مجدد از invalidate() (از نخ UI) یا postInvalidate() (از نخ پسزمینه) استفاده کنید. سیستم خود تصمیم میگیرد چه زمانی onDraw را فراخوانی کند و با ترکیب invalidateهای متعدد در یک مرحله، رندر را بهینه میکند.
دلایل: View مخفی است (setVisibility(GONE) یا INVISIBLE); View به سلسلهمراتب Window اضافه نشده است; اندازه View برابر ۰ است (width یا height = 0); setWillNotDraw(true) فراخوانی میشود (پیشفرض true برای ViewGroup). در هنگام دیباگ هر یک از این موارد را بررسی کنید.
در 60 fps، onDraw میتواند تا 60 بار در ثانیه فراخوانی شود. در 120 fps (در دستگاههای با نمایشگر 120 هرتز) — تا 120 بار. هر فراخوانی باید در ~8–16 میلیثانیه کامل شود. در انیمیشنهای فعال از طریق ObjectAnimator یا ValueAnimator، فرکانس توسط تعداد فریمهای انیمیشن تعیین میشود.
onDraw مسئول ترسیم محتوای خود View است. dispatchDraw مسئول ترسیم Viewهای فرزند (در ViewGroup) است. dispatchDraw پس از onDraw فراخوانی میشود و به صورت بازگشتی تمام عناصر فرزند را پیمایش میکند. برای View معمولی (نه ViewGroup) dispatchDraw خالی است.
بله، Canvas را میتوان برای Bitmap از طریق Canvas(bitmap) ایجاد کرد و در هر نخی روی آن ترسیم کرد. این برای پیشمحاسبه گرافیک پیچیده در پسزمینه استفاده میشود. با این حال، Canvas که در onDraw منتقل میشود متعلق به سیستم است و فقط در نخ UI در داخل متد onDraw قابل استفاده است.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید