Modifier — سلسلة المعدِّلات والأداء في Compose

المؤلف: IT Sectr نُشر: 2026-06-28 وقت القراءة: 8 دق

Modifier هو كائن غير قابل للتغيير في Jetpack Compose يحدد خصائص مكون واجهة المستخدم: الحجم، المساحة الداخلية، الخلفية، معالجة الإيماءات والسلوك. يتم دمج المعدِّلات في سلسلة من خلال استدعاءات متسلسلة، ويؤثر ترتيب تطبيقها بشكل حاسم على النتيجة. وفقًا لـ Google Android Developers, 2026، فإن الاستخدام الصحيح لـ Modifier هو أساس بناء واجهات مرنة وعالية الأداء في واجهة المستخدم التصريحية.

النقاط الرئيسية

  • Modifier هو كائن غير قابل للتغيير يصف مظهر وسلوك مكون واجهة المستخدم
  • سلسلة المعدِّلات تُبنى بشكل تسلسلي، الترتيب يؤثر على العرض
  • الترتيب مهم: padding ← size يختلف عن size ← padding
  • Modifier.composed يسمح بإنشاء معدِّلات مركبة مخصصة
  • التحسين: تجنب إعادة إنشاء Modifier في كل إعادة تركيب

ما هو Modifier في Jetpack Compose

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).

kotlin
@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

Modifier.composed هو طريقة مصنع تسمح بإنشاء معدِّلات مركبة يمكنها استخدام معدِّلات أخرى و LocalComposition والحالة المحلية. على عكس دالة الامتداد العادية، يقوم composed بإنشاء مثيل في كل مرة يُطبق فيها، مما يسمح للمعدِّل بأن يكون له حالته الخاصة.

متى تستخدم composed: مجموعات متكررة من المعدِّلات (على سبيل المثال، نمط البطاقة القياسي: padding + background + border + clickable)؛ معدِّلات بحالة (تغيير خلفية متحرك عند الضغط)؛ الوصول إلى CompositionLocals (نظام ألوان MaterialTheme، كثافة البكسل). للحالات العادية، دالة امتداد عادية بدون composed كافية.

أداء composed: كل استدعاء ينشئ كائن معدِّل جديد، مما قد يؤدي إلى تخصيصات إضافية أثناء إعادة التركيب. لمنع ذلك، لف composed في remember. توصي Google باستخدام composed فقط عندما تكون الحالة أو CompositionLocal مطلوبة بالفعل في الداخل. للمجموعات الثابتة، استخدم دوال الامتداد العادية.

kotlin
// معدِّل مخصص عبر 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 وأفضل الممارسات

تجنب إعادة إنشاء 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 مستويات متداخلة.

kotlin
// ✅ جيد: ثابت خارج 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 واحد لعدة عناصر Composable؟

نعم، Modifier غير قابل للتغيير، لذلك يمكن استخدام كائن واحد بأمان في عدة أماكن. ومع ذلك، إذا كنت تستخدم معدِّلاً composed، فإن كل استدعاء ينشئ مثيلاً جديدًا. للسلاسل الثابتة، الثابت أو val خارج Composable هو الحل الأمثل.

كيف يمكن تصحيح أخطاء سلسلة المعدِّلات؟

استخدم Layout Inspector في Android Studio — يعرض بصريًا حدود كل Modifier. للتصحيح البرمجي، أضف Modifier.border() بألوان مختلفة في كل خطوة من السلسلة لرؤية حدود كل معدِّل.

ما هو Modifier.then() وما الفرق بينه وبين الاستدعاءات المتسلسلة؟

Modifier.then(other) يلحق السلسلة other بـ this. الاستدعاءات المتسلسلة (Modifier.a().b()) مكافئة لـ Modifier.then(a()).then(b()). لا فرق — إنها نفس آلية السلسلة. then() مفيد عندما تحتاج إلى إلحاق سلسلة جاهزة من متغير.

كيف يؤثر Modifier على دلالات إمكانية الوصول؟

Modifier.semantics {} يحدد كيف سيتم وصف العنصر لقارئ الشاشة. Modifier.clickable() يضيف تلقائيًا دور الزر و Action(OnClick). للإيماءات المخصصة، تحتاج إلى تحديد semantics بشكل صريح. بدون معدِّلات دلالية، لن يتمكن مستخدمو TalkBack من التفاعل مع المكونات المخصصة.

لماذا لا يعمل background في Modifier مع الزوايا الدائرية؟

Modifier.background(color, shape) يعمل مع الزوايا، لكن clip() يجب أن يأتي قبل background لقص الزوايا. الترتيب الصحيح: clip(shape).background(color). إذا كنت بحاجة إلى قص المحتوى الداخلي أيضًا، استخدم clipToBounds() على الأصل.

الخلاصة

  • Modifier كائن غير قابل للتغيير لوصف المظهر والسلوك بشكل تصريحي
  • الترتيب يحدد النتيجة: padding → clickable يختلف عن clickable → padding
  • السلسلة تُبنى بشكل تسلسلي، كل عنصر يغلف السابق
  • Modifier.composed يسمح بإنشاء معدِّلات بحالة و CompositionLocal
  • الأداء: انقل السلاسل الثابتة إلى ثوابت، استخدم remember للديناميكية
  • الدلالات: Modifier.semantics إلزامي لإمكانية الوصول للمكونات المخصصة
  • توصية: رتب المعدِّلات من التخطيط إلى الزخرفة ثم إلى السلوك

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا