invalidate() هو أسلوب من فئة View في Android يقوم بوضع علامة على العرض على أنه بحاجة إلى إعادة الرسم. استدعاء invalidate() يؤدي إلى إعادة رسم العرض في أقرب دورة تحديث للشاشة، مما يجعله الآلية الأساسية لتحديث الحالة البصرية للمكونات المخصصة. وفقاً لوثائق Android Developers (2025)، يُستخدم invalidate() في 90% من طرق العرض المخصصة لمزامنة تغييرات البيانات مع العرض على الشاشة. يعمل الأسلوب بشكل غير متزامن — فهو فقط يضع علامة dirty ويعيد التحكم فوراً.
الخلاصة
invalidate() هو أسلوب من فئة android.view.View يُخبر نظام Android بأن التمثيل المرئي للعرض قديم. بعد استدعاء الأسلوب، يقوم النظام بوضع علامة على العرض كـ dirty ويجدد إعادة رسمه في أقرب دورة تحديث للشاشة (عادة 16 مللي ثانية لـ 60 إطاراً في الثانية).
يأتي أسلوب invalidate() بأشكال مختلفة: بدون معاملات (إعادة رسم كاملة)، مع معامل Rect (جزئي)، ومع معاملات ltrb (left, top, right, bottom). جميع الإصدارات تعمل بشكل غير متزامن ويجب استدعاؤها من خيط UI. للاستدعاء من الخيوط الخلفية يوجد postInvalidate().
آلية إعادة الرسم في Android تعتمد على ViewRootImpl — مكون داخلي يربط هيكل العرض بالسطح للرسم. عند استدعاء invalidate()، يقوم ViewRootImpl بوضع علامة على منطقة العرض كـ dirty ويرسل طلب إعادة رسم عبر Choreographer — خدمة نظام تقوم بمزامنة الرسم مع معدل تحديث الشاشة.
يتلقى Choreographer إشارة من Vsync ويبدأ تمريراً ثلاثياً: measure، layout، draw. ومع ذلك، فإن invalidate() يؤثر فقط على مرحلة draw — مراحل measure و layout لا يتم تنفيذها ما لم يتم استدعاء requestLayout(). هذا فرق رئيسي: invalidate() أرخص من requestLayout() لأنه لا يعيد حساب الهندسة.
class CustomChartView(context: Context, attrs: AttributeSet?)
: View(context, attrs) {
private var dataPoints: List<Float> = emptyList()
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
fun updateData(newPoints: List<Float>) {
dataPoints = newPoints
invalidate() // طلب إعادة رسم
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.BLUE
paint.strokeWidth = 4f
paint.style = Paint.Style.STROKE
// رسم خط الرسم البياني
val path = Path()
dataPoints.forEachIndexed { index, value ->
val x = index * width / max(dataPoints.size - 1, 1)
val y = height - value * height
if (index == 0) path.moveTo(x, y)
else path.lineTo(x, y)
}
canvas.drawPath(path, paint)
}
}
في هذا المثال، يستدعي عرض مخصص لرسم رسم بياني invalidate() عند تحديث البيانات. يقوم النظام بإعادة رسم هذا العرض فقط دون التأثير على العناصر الأخرى في التسلسل الهرمي. onDraw() يتلقى Canvas لرسم الخطوط عبر Path.
الفرق الرئيسي بين invalidate() و postInvalidate() يكمن في أمان الخيوط. invalidate() يجب استدعاؤه فقط من خيط UI (الخيط الرئيسي). postInvalidate() يمكن استدعاؤه من أي خيط — فهو يرسل طلب إعادة رسم إلى خيط UI عبر Handler.
| الخاصية | invalidate() | postInvalidate() |
|---|---|---|
| خيط الاستدعاء | خيط UI (الخيط الرئيسي) | أي خيط |
| الآلية | تحديث مباشر لعلامة dirty | عبر Handler.post() إلى خيط UI |
| زمن الاستجابة | أدنى حد، في الدورة الحالية | حتى الدورة التالية لخيط UI |
| الأداء | عالٍ | عبء بسيط على Handler |
| التوصية | استخدم invalidate() دائماً لخيط UI | فقط للخيوط الخلفية |
عملياً، يُستخدم postInvalidate() في سيناريوهات تحميل البيانات من الشبكة، معالجة نتائج المستشعرات، أو الحسابات الخلفية. إذا كنت في خيط UI — استخدم دائماً invalidate() للحصول على أقل زمن استجابة.
// تم الاستدعاء من خيط UI
view.invalidate()
// تم الاستدعاء من خيط خلفي
Thread {
// حسابات ثقيلة
val result = performHeavyCalculation()
runOnUiThread {
updateUi(result)
}
}.start()
invalidate(Rect) و invalidate(int l, int t, int r, int b) تسمح بتحديد منطقة إعادة الرسم. هذا أمر بالغ الأهمية للأداء: عندما يتغير جزء فقط من العرض (مثل حركة المؤشر، تغيير المؤشر)، ليست هناك حاجة لإعادة رسم العرض بالكامل.
يقوم النظام بتمرير المستطيل dirty المحدد إلى onDraw() عبر canvas.clipBounds. داخل onDraw()، يمكنك التحقق من clipBounds والرسم فقط داخل تلك المنطقة، على الرغم من أن Android Canvas يقوم تلقائياً بقص الرسم خارج المستطيل dirty.
// تحديث جزئي: منطقة المؤشر فقط
private val cursorRect = Rect()
fun moveCursorTo(newX: Int, newY: Int) {
// إبطال الموضع القديم
invalidate(cursorRect)
cursorRect.set(newX - 5, newY - 5,
newX + 5, newY + 5)
// إبطال الموضع الجديد
invalidate(cursorRect)
}
بدون إعادة رسم جزئي، كل حركة مؤشر كانت ستؤدي إلى إعادة رسم العرض بالكامل، وهو ما يعني لرسم بياني كبير إعادة رسم آلاف البكسلات بدلاً من بضع عشرات. invalidate(Rect) هي تقنية أساسية للمحررات، أسطح الرسم، والمكونات المتحركة.
أحد الأخطاء الشائعة هو استدعاء requestLayout() حيث يكفي invalidate()، والعكس صحيح. الفرق جوهري: invalidate() يؤثر فقط على مرحلة draw، بينما requestLayout() يؤدي إلى دورة كاملة measure → layout → draw.
| الجانب | invalidate() | requestLayout() |
|---|---|---|
| مراحل الدورة | draw فقط | measure + layout + draw |
| متى تستخدم | يتغير فقط التقديم (اللون، النص، الرسومات) | يتغير حجم أو موضع العرض |
| الأداء | خفيف — إعادة رسم فقط | ثقيل — إعادة حساب التسلسل الهرمي |
| التأثير على التسلسل الهرمي | فقط العرض الحالي | قد يؤثر على الحاويات الأم |
إذا قمت بتغيير النص في TextView، فإن invalidate() كافٍ لأن حجم العرض لا يتغير. إذا كان النص قد ينتقل إلى سطر جديد ويزيد الارتفاع، فأنت بحاجة إلى requestLayout(). Android Lint يساعد في تتبع هذه الأخطاء من خلال قواعد الأداء.
الاستدعاءات المفرطة لـ invalidate() هي أحد الأسباب الرئيسية لضعف أداء طرق العرض المخصصة في Android. دعنا نستعرض تقنيات التحسين.
إذا كانت البيانات تتحدث بتردد عالٍ (المستشعرات، الرسوم المتحركة، الفيديو)، لا تستدعِ invalidate() عند كل تغيير. استخدم ValueAnimator أو Choreographer.FrameCallback للمزامنة مع معدل تحديث الشاشة. هذا يضمن استدعاء invalidate() ليس أكثر من مرة واحدة لكل إطار.
منذ API 14، يدعم Android تسريع العتاد عبر GPU. إذا كان عرضك المخصص يستخدم فقط Canvas API (drawRect، drawCircle، drawPath)، فإن التسريع يعمل بشفافية. للعمليات المتوافقة مع DisplayList، تتم معالجة invalidate() بشكل أسرع بكثير.
// استخدام Choreographer لمزامنة Vsync
private val frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
updateAnimation(frameTimeNanos)
invalidate()
Choreographer.getInstance().postFrameCallback(this)
}
fun startAnimation() {
Choreographer.getInstance().postFrameCallback(frameCallback)
}
استخدم invalidate(Rect) للتحديثات المستهدفة، تجنب استدعاء invalidate() من onDraw() (حلقة لا نهائية)، وقم دائماً بقياس الأداء عبر GPU Profile Rendering على جهاز. هذا سيظهر الوقت الدقيق لتقديم كل إطار ويساعد في تحديد المناطق المشكلة.
الأسئلة الشائعة
لا، استدعاء invalidate() داخل onDraw() ينشئ حلقة لا نهائية من إعادة الرسم: onDraw() يستدعي invalidate()، والذي يشغل onDraw() مرة أخرى. هذا يؤدي إلى استخدام 100% من المعالج وتساقط الإطارات. استخدم الرسوم المتحركة عبر ValueAnimator أو Choreographer.
invalidate() يعمل فقط في خيط UI ويحدث علامة dirty فوراً. postInvalidate() يرسل طلباً عبر Handler إلى خيط UI ويمكن استدعاؤه من أي خيط خلفي. إذا كنت في خيط UI — استخدم invalidate() للحصول على أقل زمن استجابة.
نعم، داخلياً يستدعي أسلوب setText() في TextView invalidate() بعد تحديث النص. إذا غير النص أبعاد العرض، يتم أيضاً استدعاء requestLayout(). لا يحتاج المطور إلى استدعاء invalidate() يدوياً عند العمل مع الأدوات القياسية.
كل استدعاء لـ invalidate() يجدول إعادة رسم في Vsync التالي (كل 16 مللي ثانية). إذا استغرق onDraw() أكثر من 16 مللي ثانية، يحدث تساقط للإطارات. حسّن onDraw() — قم بتخزين Bitmaps مؤقتاً، تجنب التخصيصات، واستخدم تسريع العتاد لتقديم GPU.
نعم، بعد تغيير خصائص Paint (اللون، السمك، النمط)، يجب استدعاء invalidate()، لأن العرض لا يتتبع تغييرات كائنات Paint تلقائياً. النظام لا يعلم أن Paint قد تغير ولن يستدعي onDraw() بدون طلب صريح.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا