Canvas في Android — هي فئة من حزمة android.graphics توفر API للتصيير ثنائي الأبعاد على View. يعمل Canvas كلوحة رسم يقوم المطور برسم الأشكال الهندسية والنصوص والصور والمسارات عليها باستخدام طرق draw* المختلفة. وفقًا لوثائق مطوري Android (2025)، فإن Canvas هو الأداة الرئيسية للتصيير المخصص في Android ويُستخدم مع فئة Paint لتعريف الأنماط والألوان. كل استدعاء لـ onDraw() يتلقى مثيل Canvas جاهزًا مرتبطًا بمخزن bitmap الخاص بالعرض.
النقاط الرئيسية
Canvas — هي الفئة المركزية للرسومات ثنائية الأبعاد في Android التي توفر طرقًا للرسم على سطح. كل Canvas مرتبط بـ Bitmap — منطقة ذاكرة تُسجل فيها نتائج عمليات الرسم. عندما يُعاد رسم View، يمرر النظام إلى onDraw() Canvas مرتبطًا بالفعل بمخزن تلك View.
يعمل Canvas في نظام إحداثيات حيث (0,0) هي الزاوية العليا اليسرى، المحور X يتجه لليمين، المحور Y يتجه للأسفل. جميع عمليات الرسم تستخدم الحالة الحالية لـ Canvas، بما في ذلك مصفوفة التحويل، منطقة القص ونمط Paint. وفقًا لوثائق Android (2025)، تتضمن API Canvas أكثر من 30 طريقة draw* لأنواع مختلفة من البدائيات الرسومية.
منذ Android 3.0 (API 11) يدعم Canvas تسريع الأجهزة عبر DisplayList. في الوضع المسرّع، تُسجل عمليات Canvas في DisplayList ثم تُنفذ على GPU. هذا يعطي زيادة كبيرة في الأداء لكنه يفرض قيودًا: بعض عمليات Canvas (clipPath مع قص غير Path) غير مدعومة في الوضع المسرّع.
يوفر Canvas مجموعة من الطرق لرسم البدائيات الأساسية. كل طريقة تقبل معلمات هندسية وكائن Paint يحدد النمط البصري.
| الطريقة | الغرض | مثال |
|---|---|---|
| drawRect() | رسم مستطيل | canvas.drawRect(10f, 10f, 100f, 100f, paint) |
| drawCircle() | رسم دائرة | canvas.drawCircle(50f, 50f, 30f, paint) |
| drawLine() | رسم خط | canvas.drawLine(0f, 0f, 100f, 100f, paint) |
| drawText() | رسم نص | canvas.drawText(“Hello”, 10f, 20f, paint) |
| drawBitmap() | رسم صورة | canvas.drawBitmap(bitmap, 0f, 0f, paint) |
| drawPath() | رسم مسار تعسفي | canvas.drawPath(path, paint) |
| drawOval() | رسم بيضاوي | canvas.drawOval(rectF, paint) |
| drawArc() | رسم قوس أو قطاع | canvas.drawArc(rectF, 0f, 90f, true, paint) |
يمكن استدعاء كل طريقة بأي ترتيب — يرسم Canvas بترتيب الاستدعاءات، مكدسًا العناصر الجديدة فوق العناصر السابقة. للتكوينات المعقدة، تُستخدم تقنية التصيير متعدد الطبقات مع حفظ واستعادة حالة Canvas.
Paint — هي الفئة التي تحدد كيف سيرسم Canvas. بدون Paint، طرق Canvas عديمة الفائدة — فهي تحدد اللون، سمك الخط، نمط التعبئة، حجم النص، التأثيرات (الظل، التدرج، التمويه) ووضع المزج.
لـ Paint خصائص رئيسية: color (اللون)، style (FILL أو STROKE أو FILL_AND_STROKE)، strokeWidth (سمك الخط)، textSize (حجم النص)، isAntiAlias (تنعيم الحواف)، shader (التدرج أو الصورة). إنشاء كائن Paint عملية مكلفة، لذا يجب إنشاؤه مرة واحدة وإعادة استخدامه.
private val fillPaint = Paint().apply {
color = Color.parseColor("#FF6200EE")
style = Paint.Style.FILL
isAntiAlias = true
}
private val strokePaint = Paint().apply {
color = Color.WHITE
style = Paint.Style.STROKE
strokeWidth = 4f
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(100f, 100f, 80f, fillPaint)
canvas.drawCircle(100f, 100f, 80f, strokePaint)
}
في هذا المثال، يُستخدم كائنان Paint: واحد لتعبئة الدائرة باللون الأرجواني، والثاني للحد الأبيض. فصل التعبئة والحد هو ممارسة قياسية تسمح بالتحكم المرن في المظهر دون إعادة إنشاء الكائنات.
للتعبئات المتدرجة، يقبل Paint كائن Shader — الفئات الفرعية LinearGradient و RadialGradient و SweepGradient. يُربط Shader بـ Paint عبر الطريقة setShader() ويحدد كيف يتغير اللون عبر منطقة الرسم.
يدعم Canvas تحويلات المصفوفة لنظام الإحداثيات: translate() (الإزاحة)، rotate() (التدوير)، scale() (التحجيم) و skew() (الإمالة). تتراكم التحويلات: كل استدعاء لاحق يُطبق فوق السابق.
تتم إدارة مكدس التحويلات عبر save() و restore(). تحفظ save() الحالة الحالية لـ Canvas (المصفوفة، القص) في المكدس، وتستعيد restore() آخر حالة محفوظة. بين save() و restore() يمكن تطبيق تحويلات متداخلة.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.save() // Save initial state
canvas.translate(100f, 100f) // Translate origin
// First square
canvas.drawRect(0f, 0f, 50f, 50f, fillPaint)
canvas.save() // Save after translate
canvas.rotate(45f, 25f, 25f) // Rotate around square center
// Rotated square
canvas.drawRect(0f, 0f, 50f, 50f, strokePaint)
canvas.restore() // Restore after rotate
canvas.restore() // Restore initial state
}
استخدم دائمًا save() قبل بدء التحويلات و restore() بعد اكتمالها. تؤدي أزواج save/restore غير المتوازنة إلى تراكم المصفوفة وتصيير غير صحيح للعناصر اللاحقة.
للرسومات المعقدة، يُدمج Canvas مع Bitmap و Path. يُستخدم Bitmap لتحميل الصور وتخزين الإطارات المُصيّرة مؤقتًا. يُستخدم Path لبناء منحنيات بيزير التعسفية والمضلعات والخطوط المعقدة.
تقنية التصيير خارج الشاشة: يتم إنشاء Bitmap، ورسم مشهد معقد عليه عبر Canvas منفصل، ثم إخراج Bitmap النهائي في onDraw() عبر drawBitmap(). هذا يسمح بتحديث الرسومات المعقدة دون إعادة رسم كاملة في كل إطار.
private var offscreenBitmap: Bitmap? = null
private var offscreenCanvas: Canvas? = null
fun buildOffscreenScene(width: Int, height: Int) {
offscreenBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
offscreenCanvas = Canvas(offscreenBitmap)
// Render scene once
offscreenCanvas!!.drawColor(Color.WHITE)
drawGrid(offscreenCanvas!!)
drawComplexShape(offscreenCanvas!!)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
offscreenBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
}
Path يُستخدم لبناء خطوط معقدة: منحنيات بيزير (quadTo، cubicTo)، الأقواس (arcTo)، الخطوط (lineTo). يمكن إغلاق Path (close()) للأشكال المملوءة أو فتحه للخطوط. الطريقة الرئيسية drawPath() تدمج Path و Paint للتصيير النهائي.
أداء تصيير Canvas يؤثر مباشرة على FPS للتطبيق. المشكلات الرئيسية: التخصيصات المفرطة في onDraw()، استدعاءات invalidate() المتكررة والاستخدام غير الأمثل لتسريع الأجهزة.
يجب إنشاء Paint و Path و Rect و RectF والكائنات الأخرى مرة واحدة في المُهيئ أو طريقة setup(). كل تخصيص في onDraw() يخلق ضغطًا على جامع القمامة، مما يؤدي إلى تأخيرات دقيقة. الاستثناء هو static أو companion object للكائنات غير القابلة للتغيير.
إذا كان معظم View مخفيًا خارج المنطقة المرئية (مثلاً في ScrollView)، استخدم canvas.clipRect() لتحديد التصيير. تسريع الأجهزة في هذه الحالة يحسن additionally قص البكسلات غير المرئية.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Clip to visible area
canvas.save()
canvas.clipRect(scrollX, scrollY, scrollX + width, scrollY + height)
// Render large scene
renderScene(canvas)
canvas.restore()
}
استخدم View.setLayerType() مع LAYER_TYPE_HARDWARE لـ Views ذات الرسومات Canvas المعقدة التي تُحدّث نادرًا. للرسوم المتحركة التي تُحدّث بشكل متكرر، استخدم LAYER_TYPE_NONE لتجنب التحديث المكلف للطبقة المسرّعة. قم بالتحليل باستخدام GPU Profile Rendering و Android Studio Profiler.
الأسئلة الشائعة
نعم، يمكن إنشاء Canvas(Bitmap) — مُنشئ يقبل Bitmap. يُستخدم هذا للتصيير خارج الشاشة: ترسم على Bitmap ثم تخرجه عبر drawBitmap(). يمكن أيضًا إنشاء Canvas من Surface (لـ SurfaceView) أو عبر Picture.
Canvas — هو API للرسم على View في سلسلة واجهة المستخدم. SurfaceView — هو Surface منفصل يمكن تحديثه من سلسلة خلفية عبر Canvas الخاص به (lockCanvas() / unlockCanvasAndPost()). SurfaceView مفضل للألعاب والفيديو.
السبب الأكثر شيوعًا هو أزواج save() و restore() غير المتوازنة. إذا لم يتم استدعاء restore() بعد التحويل (translate، rotate)، يبقى Canvas في نظام إحداثيات معدّل، وتُرسَم العناصر اللاحقة مع إزاحة. تحقق من أزواج save()/restore().
استخدم Paint.setTextAlign(Paint.Align.CENTER) للمحاذاة الأفقية و Paint.getTextBounds() أو Paint.descent() / ascent() للعمودية. التوسيط: canvas.drawText(نص, centerX, centerY - (ascent + descent) / 2, paint).
Canvas بحد ذاته لا يقوم بالتحريك — فهو يرسم الحالة الحالية فقط. يتم تحقيق الحركة من خلال استدعاءات متتابعة لـ invalidate() (أو postInvalidateOnAnimation())، مع رسم إطار جديد في onDraw() في كل مرة. للرسوم المتحركة السلسة استخدم ValueAnimator أو Choreographer للمزامنة مع Vsync.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.