onDraw() هو ملخص رئيسي من فئة View في Android SDK المسؤول عن رسم محتوى العرض. يقوم النظام باستدعاء onDraw في كل تحديث لمظهر View — أثناء العرض الأولي، بعد invalidate() أو عند تغير الأبعاد. وفقًا لوثائق Android Developers (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 (تخطيط جديد)؛ 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. قبل استدعاء ملخصات الرسم، يمكن للمطور تغيير نظام الإحداثيات من خلال canvas.translate(dx, dy)، canvas.rotate(degrees, px, py)، canvas.scale(sx, sy, px, py). ستتم جميع عمليات الرسم اللاحقة في النظام المحول.
onDraw يتم تنفيذه على خط واجهة المستخدم الرئيسي، وتؤثر مدته مباشرة على معدل الإطارات (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 (التحويل والقص) على مكدسة. canvas.restore() يستعيد آخر حالة محفوظة. هذا ضروري للتحويلات المؤقتة: احفظ الحالة، طبق translate/rotate، ارسم، ثم استعد. save و restore خفيفان ولا يؤثران على الأداء عند استخدامهما بشكل معقول (أقل من 10–15 مستوى تداخل).
invalidate() هو ملخص View يخبر النظام أن محتوى View قديم ويحتاج إلى إعادة الرسم. يبدأ استدعاء invalidate دورة draw جديدة يتم فيها استدعاء onDraw. يجب استدعاء invalidate فقط من خط واجهة المستخدم (الخط الرئيسي). للاستدعاء من خط خلفي، يستخدم postInvalidate.
postInvalidate() هي نسخة آمنة للخيوط من invalidate ترسل طلب إعادة الرسم إلى طابور الخط الرئيسي عبر Handler. تستخدم في مهام الخلفية عندما يحتاج View إلى التحديث بعد اكتمال تحميل البيانات أو الحسابات. يمكن دمجها مع 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("Hello from 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() (من خط واجهة المستخدم) أو postInvalidate() (من خط خلفي). سيقرر النظام متى يستدعي onDraw وسيحسن الرسم بدمج استدعاءات invalidate متعددة في مرة واحدة.
الأسباب: View مخفية (setVisibility(GONE) أو INVISIBLE)؛ لم تتم إضافة View إلى تسلسل Window؛ حجم View يساوي 0 (العرض أو الارتفاع = 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 ينتمي إلى النظام ويمكن استخدامه فقط على خط واجهة المستخدم داخل ملخص onDraw.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.