Modifier هو كائن غير قابل للتغيير في Jetpack Compose يحدد خصائص مكون واجهة المستخدم: الحجم، المساحة الداخلية، الخلفية، معالجة الإيماءات والسلوك. يتم دمج المعدِّلات في سلسلة من خلال استدعاءات متسلسلة، ويؤثر ترتيب تطبيقها بشكل حاسم على النتيجة. وفقًا لـ Google Android Developers, 2026، فإن الاستخدام الصحيح لـ Modifier هو أساس بناء واجهات مرنة وعالية الأداء في واجهة المستخدم التصريحية.
النقاط الرئيسية
Modifier هي واجهة من حزمة androidx.compose.ui تطبق نمط Composite. كل معدِّل هو عنصر سلسلة يغلف العنصر السابق ويضيف سلوكه الخاص. Modifier غير قابل للتغيير — أي تغيير ينشئ كائنًا جديدًا عن طريق النسخ مع إضافة عنصر جديد إلى السلسلة. هذا يسمح بمشاركة Modifier واحد بأمان بين مكونات متعددة.
يتم استدعاء دوال المعدِّل الأساسية من خلال الكائن المرافق Modifier (على سبيل المثال، Modifier.padding()، Modifier.fillMaxWidth()). كل دالة ترجع Modifier جديدًا مع العنصر المضاف. إذا كان هناك عدة معدِّلات، يتم دمجها في سلسلة: Modifier.padding(16.dp).fillMaxWidth().background(Color.Blue). الترتيب هو من الخارج إلى الداخل بالنسبة لعنصر واجهة المستخدم.
على عكس طرق العرض التقليدية حيث كانت الخصائص تُضبط عبر setters (view.setPadding(...)، view.setBackground(...))، في Compose Modifier هو وصف تصريحي. المكون لا "يطبق" المعدِّلات في وقت التشغيل — LayoutNode يجتاز سلسلة Modifier أثناء التركيب ويبني قائمة من Modifier.Element التي تتم معالجتها بعد ذلك خلال مرحلتي القياس والترتيب.
ترتيب المعدِّلات هو أحد أكثر الأخطاء شيوعًا في Compose. كل معدِّل يغلف السابق، ويتم تنفيذ العمليات من الخارج إلى الداخل. على سبيل المثال، padding(16.dp).clickable { }: أولاً تضاف المساحة حول العنصر، ثم تشمل منطقة النقر المساحة. clickable { }.padding(16.dp): منطقة النقر تساوي حجم العنصر أولاً، ثم تضاف المساحة حوله — النقر على المساحة لن يعمل.
قاعدة التذكر: اقرأ السلسلة من اليسار إلى اليمين وطبق من الخارج إلى الداخل. المعدِّل الأول هو الأبعد، يُطبق على المنطقة حول العنصر. الأخير هو الأعمق، يُطبق مباشرة على المحتوى. يجب أن تأتي معدِّلات الحجم (size، fillMaxWidth) بعد المساحة إذا كانت المساحة مطلوبة من الأصل، أو قبل المساحة إذا كان المحتوى يجب أن يُقيَّد أولاً ثم يُوسَّط.
مثال: size(100.dp).padding(10.dp) — عنصر بحجم ثابت 100dp، ثم مساحة 10dp من الخارج (الحجم النهائي 120dp). padding(10.dp).size(100.dp) — المساحة 10dp تقلل المساحة المتاحة إلى (الأصل - 20dp)، ثم size(100dp) قد يفيض عن الأصل. فكر دائمًا في الترتيب بتأنٍ، باستخدام اختبارات العرض للتحقق من النتيجة.
| الترتيب | النتيجة |
|---|---|
| padding ← clickable | النقر يعمل أيضًا على منطقة المساحة |
| clickable ← padding | النقر يعمل فقط على المحتوى، المساحة منطقة ميتة |
| size ← padding | عنصر size(100)، مساحة من الخارج ← 100+2*pad |
| padding ← size | المساحة تقلل المكان، size قد يتجاوز الحدود |
| background ← padding | الخلفية تملأ العنصر بأكمله بما في ذلك المنطقة الخارجية |
| padding ← background | الخلفية فقط داخل المساحة (المنطقة الخارجية شفافة) |
مكتبة Compose القياسية تتضمن ~50+ معدِّلاً مقسمة إلى فئات. الحجم والتمركز: Modifier.size()، width()، height()، fillMaxSize()، fillMaxWidth()، fillMaxHeight()، defaultMinSize()، requiredSize(). المساحة والحدود: padding()، offset()، margin (يُضبط عبر مساحة الأصل أو Layout). الزخرفة: background()، border()، clip()، alpha()، shadow()، blur().
السلوك والإيماءات: clickable()، combinedClickable()، pointerInput()، draggable()، swipeable(). الترتيب في الحاوية: weight() (لـ Row/Column)، align()، alignBy()، matchParentSize(). الدلالات وإمكانية الوصول: semantics()، testTag()، clearAndSetSemantics(). الرسم: drawBehind()، drawWithContent()، drawModifier() — معدِّلات تسمح بالرسم المخصص على اللوحة.
المعدِّلات الدلالية هي فئة خاصة. Modifier.semantics {} يحدد كيف سيتم تمثيل العنصر في شجرة إمكانية الوصول. Compose يملأ الدلالات تلقائيًا من النص، لكن المكونات المخصصة تحتاج إلى أدوار وحالات وإجراءات محددة يدويًا. هذا أمر بالغ الأهمية للامتثال لمعيار WCAG 2.2 والتشغيل الصحيح لـ TalkBack (Android) و VoiceOver (iOS).
@Composable
fun ModifierDemo() {
// سلسلة المعدِّلات بالترتيب الصحيح
Box(
modifier = Modifier
.size(150.dp)
.padding(8.dp)
.border(2.dp, Color.Gray)
.background(Color(0xFFE3F2FD))
.clickable { /* handle click */ }
.semantics {
contentDescription = "Demo card with click action"
role = Role.Button
}
) {
Text("المسني")
}
}
Modifier.composed هو طريقة مصنع تسمح بإنشاء معدِّلات مركبة يمكنها استخدام معدِّلات أخرى و LocalComposition والحالة المحلية. على عكس دالة الامتداد العادية، يقوم composed بإنشاء مثيل في كل مرة يُطبق فيها، مما يسمح للمعدِّل بأن يكون له حالته الخاصة.
متى تستخدم composed: مجموعات متكررة من المعدِّلات (على سبيل المثال، نمط البطاقة القياسي: padding + background + border + clickable)؛ معدِّلات بحالة (تغيير خلفية متحرك عند الضغط)؛ الوصول إلى CompositionLocals (نظام ألوان MaterialTheme، كثافة البكسل). للحالات العادية، دالة امتداد عادية بدون composed كافية.
أداء composed: كل استدعاء ينشئ كائن معدِّل جديد، مما قد يؤدي إلى تخصيصات إضافية أثناء إعادة التركيب. لمنع ذلك، لف composed في remember. توصي Google باستخدام composed فقط عندما تكون الحالة أو CompositionLocal مطلوبة بالفعل في الداخل. للمجموعات الثابتة، استخدم دوال الامتداد العادية.
// معدِّل مخصص عبر composed مع حالة
fun Modifier.cardStyle(
elevation: Dp = 4.dp,
isSelected: Boolean = false
): Modifier = this.composed {
val backgroundColor = if (isSelected)
MaterialTheme.colorScheme.primaryContainer
else
MaterialTheme.colorScheme.surface
this
.fillMaxWidth()
.padding(12.dp)
.background(backgroundColor, RoundedCornerShape(8.dp))
.shadow(elevation, RoundedCornerShape(8.dp))
}
// مثال استخدام
@Composable
fun CardList() {
Column {
Box(Modifier.cardStyle()) { Text("العنصر 1") }
Box(Modifier.cardStyle(isSelected = true)) { Text("محدد") }
}
}
// النسخة الثابتة (بدون composed) — أسرع
fun Modifier.simpleCardStyle(): Modifier =
this.fillMaxWidth().padding(8.dp).clip(RoundedCornerShape(4.dp))
تجنب إعادة إنشاء Modifier في كل إعادة تركيب. إذا كان المعدِّل لا يعتمد على بيانات قابلة للتغيير — انقله إلى ثابت أو remember. كل استدعاء لـ Modifier.padding().background() ينشئ كائنات Modifier.Element جديدة. في مكون معزول هذا غير ملحوظ، لكن في LazyColumn بمئات العناصر، تسبب التخصيصات الإضافية تأخيرًا ملحوظًا في التمرير.
قاعدة: إذا كانت سلسلة المعدِّلات لا تعتمد على معاملات دالة Composable — أعلنها كـ val خارج الدالة (على مستوى الملف أو Companion). إذا كانت تعتمد — استخدم remember(التبعية) { ... }. للمعدِّلات المتطابقة دائمًا، فإن val خارج Composable هو الأكثر كفاءة: يتم إنشاء هذه الكائنات مرة واحدة طوال عمر التطبيق.
أفضل ممارسات ترتيب Modifier: ضع المعدِّلات بترتيب منطقي: أولاً الحجم/المساحة (تخطيط)، ثم الزخرفة (background، border)، ثم السلوك (clickable، pointerInput). هذا لا يحسن قابلية القراءة فحسب، بل يساعد أيضًا Compose Runtime على تحسين السلسلة أثناء القياس. تجنب أيضًا عناصر Box المتداخلة المفرطة مع Modifier مختلفة — غالبًا ما يمكن لمعدِّل واحد على الحاوية الأصل أن يحل محل 2-3 مستويات متداخلة.
// ✅ جيد: ثابت خارج Composable
private val cardModifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
@Composable
fun CardContent() {
Box(cardModifier.background(Color.White)) { ... }
}
// ❌ سيئ: إعادة إنشاء في كل إعادة تركيب
@Composable
fun BadCard() {
Box(Modifier.fillMaxWidth().padding(16.dp)) { ... }
}
// ✅ جيد: remember لمعدِّل ديناميكي
@Composable
fun DynamicCard(color: Color) {
val modifier = remember(color) {
Modifier.fillMaxWidth().background(color)
}
Box(modifier) { ... }
}
الأسئلة الشائعة
نعم، Modifier غير قابل للتغيير، لذلك يمكن استخدام كائن واحد بأمان في عدة أماكن. ومع ذلك، إذا كنت تستخدم معدِّلاً composed، فإن كل استدعاء ينشئ مثيلاً جديدًا. للسلاسل الثابتة، الثابت أو val خارج Composable هو الحل الأمثل.
استخدم Layout Inspector في Android Studio — يعرض بصريًا حدود كل Modifier. للتصحيح البرمجي، أضف Modifier.border() بألوان مختلفة في كل خطوة من السلسلة لرؤية حدود كل معدِّل.
Modifier.then(other) يلحق السلسلة other بـ this. الاستدعاءات المتسلسلة (Modifier.a().b()) مكافئة لـ Modifier.then(a()).then(b()). لا فرق — إنها نفس آلية السلسلة. then() مفيد عندما تحتاج إلى إلحاق سلسلة جاهزة من متغير.
Modifier.semantics {} يحدد كيف سيتم وصف العنصر لقارئ الشاشة. Modifier.clickable() يضيف تلقائيًا دور الزر و Action(OnClick). للإيماءات المخصصة، تحتاج إلى تحديد semantics بشكل صريح. بدون معدِّلات دلالية، لن يتمكن مستخدمو TalkBack من التفاعل مع المكونات المخصصة.
Modifier.background(color, shape) يعمل مع الزوايا، لكن clip() يجب أن يأتي قبل background لقص الزوايا. الترتيب الصحيح: clip(shape).background(color). إذا كنت بحاجة إلى قص المحتوى الداخلي أيضًا، استخدم clipToBounds() على الأصل.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا