إنشاء عناصر التعديل

تتيح لك المعدِّلات تزيين عنصر قابل للإنشاء أو تحسينه. تتيح لك المعدِّلات تنفيذ أنواع الإجراءات التالية:

  • تغيير حجم العنصر القابل للإنشاء وتنسيقه وسلوكه ومظهره
  • إضافة معلومات، مثل تصنيفات تسهيل الاستخدام
  • معالجة بيانات أدخلها المستخدم
  • إضافة تفاعلات عالية المستوى، مثل جعل عنصر قابلاً للنقر أو التمرير أو السحب أو التكبير

المعدِّلات هي عناصر Kotlin عادية. أنشئ معدِّلاً من خلال استدعاء إحدى دوال الفئة Modifier:

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

سطران من النص على خلفية ملونة، مع مساحة فارغة حول النص

يمكنك ربط هذه الدوال معًا لإنشاء دالة مركّبة:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

تم الآن توسيع الخلفية الملوّنة خلف النص لتشغل عرض الجهاز بالكامل.

في الرمز البرمجي أعلاه، لاحظ دوال المعدِّل المختلفة المستخدَمة معًا.

  • تضيف السمة padding مساحة حول أحد العناصر.
  • تجعل السمة fillMaxWidth العنصر القابل للإنشاء يملأ الحد الأقصى للعرض الذي يوفّره له العنصر الرئيسي.

من أفضل الممارسات أن تقبل جميع العناصر القابلة للإنشاء مَعلمة modifier ، وأن يتم تمرير هذا المعدِّل إلى العنصر الثانوي الأول الذي يعرض واجهة المستخدم. ويؤدي ذلك إلى جعل الرمز البرمجي أكثر قابلية لإعادة الاستخدام، كما يجعل سلوكه أكثر قابلية للتوقّع وأكثر سهولة. لمزيد من المعلومات، يُرجى الاطّلاع على إرشادات Compose API، تقبل العناصر وتراعي مَعلمة Modifier.

ترتيب المعدِّلات مهم

ترتيب دوال المعدِّل مهم. بما أنّ كل دالة تُجري تغييرات على Modifierالتي تعرضها الدالة السابقة، يؤثر التسلسل في النتيجة النهائية. إليك مثالاً على ذلك:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

تستجيب المساحة بأكملها للنقرات، بما في ذلك المساحة المتروكة حول الحواف

في الرمز أعلاه، يمكن النقر على المنطقة بأكملها، بما في ذلك المساحة المتروكة المحيطة، لأنّه تم تطبيق المعدِّل padding بعد المعدِّل clickable. إذا تم عكس ترتيب المعدِّلات، لن تستجيب المسافة المضافة بواسطة padding لإدخال المستخدم:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

لم يعُد الحشو حول حافة التصميم يستجيب للنقرات

معدّلات مدمجة

توفّر Jetpack Compose قائمة بمعدِّلات مدمَجة لمساعدتك في تزيين أو تحسين عنصر قابل للإنشاء. في ما يلي بعض المعدِّلات الشائعة التي ستستخدمها لتعديل تنسيقاتك.

padding وsize

تلتف التصاميم المتوفّرة في Compose حول العناصر التابعة لها تلقائيًا. ومع ذلك، يمكنك ضبط حجم باستخدام المعدِّل size:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

يُرجى العِلم أنّه قد لا يتم الالتزام بالحجم الذي حدّدته إذا لم يستوفِ القيود الواردة من العنصر الرئيسي للتصميم. إذا كنت تريد أن يكون حجم العنصر القابل للإنشاء ثابتًا بغض النظر عن القيود الواردة، استخدِم المعدِّل requiredSize:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

حجم الصورة الفرعية أكبر من القيود الواردة من الصورة الرئيسية

في هذا المثال، حتى مع ضبط height الرئيسي على 100.dp، سيكون ارتفاع Image هو 150.dp، لأنّ المعدِّل requiredSize له الأولوية.

إذا أردت أن تملأ تخطيطًا فرعيًا كل الارتفاع المتاح الذي يسمح به التخطيط الرئيسي، أضِف المعدِّل fillMaxHeight (توفّر Compose أيضًا fillMaxSize وfillMaxWidth):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

ارتفاع الصورة هو نفس ارتفاع العنصر الأصل

لإضافة مساحة متروكة حول أحد العناصر، اضبط المعدِّل padding.

إذا أردت إضافة مساحة متروكة أعلى خط الأساس للنص لتحقيق مسافة معيّنة من أعلى التصميم إلى خط الأساس، استخدِم المعدِّل paddingFromBaseline:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

نص مع مساحة ملء فوقه

فرق التوقيت

لوضع تصميم بالنسبة إلى موضعه الأصلي، أضِف المعدِّل offset واضبط الإزاحة على المحورين x وy. يمكن أن تكون الإزاحات موجبة أو غير موجبة. الفرق بين padding وoffset هو أنّ إضافة offset إلى عنصر قابل للإنشاء لا تغيّر قياساته:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

تم نقل النص إلى الجانب الأيسر من الحاوية الرئيسية

يتم تطبيق المعدِّل offset أفقيًا وفقًا لاتجاه التنسيق. في سياق من اليسار إلى اليمين، يؤدي offset الموجب إلى نقل العنصر إلى اليمين، بينما يؤدي في سياق من اليمين إلى اليسار إلى نقل العنصر إلى اليسار. إذا كنت بحاجة إلى ضبط إزاحة بدون مراعاة اتجاه التنسيق، اطّلِع على المعدِّل absoluteOffset، حيث تؤدي قيمة الإزاحة الموجبة دائمًا إلى نقل العنصر إلى اليسار.

يوفّر المعدِّل offset حمولتين زائدتين، هما offset التي تأخذ الإزاحات كمعلَمات، وoffset التي تأخذ تعبير lambda. للحصول على معلومات أكثر تفصيلاً حول الحالات التي يجب فيها استخدام كل من هذه الطرق وكيفية تحسين الأداء، يمكنك الاطّلاع على قسم أداء Compose - تأجيل عمليات القراءة لأطول فترة ممكنة.

أمان النطاق في Compose

في Compose، هناك معدِّلات لا يمكن استخدامها إلا عند تطبيقها على عناصر تابعة لأنواع معيّنة من العناصر القابلة للإنشاء. تفرض Compose ذلك من خلال النطاقات المخصّصة.

على سبيل المثال، إذا أردت أن يكون حجم العنصر التابع مساويًا لحجم العنصر الرئيسي Box بدون التأثير في حجم Box، استخدِم المعدِّل matchParentSize. لا يتوفّر matchParentSize إلا في BoxScope. لذلك، لا يمكن استخدامه إلا على حساب طفل ضمن حساب أحد الوالدَين في Box.

تمنعك ميزة "أمان النطاق" من إضافة معدِّلات لا تعمل في عناصر أخرى قابلة للإنشاء ونطاقات أخرى، كما أنّها توفّر الوقت الذي قد تستغرقه تجربة المعدِّلات المختلفة.

تُعلم المعدّلات ذات النطاق المحدود الوالد ببعض المعلومات التي يجب أن يعرفها عن الطفل. ويُشار إلى هذه العناصر أيضًا باسم معدِّلات البيانات الرئيسية. تختلف هذه المعدِّلات في طريقة عملها عن المعدِّلات العامة، ولكن من ناحية الاستخدام، لا تهمّ هذه الاختلافات.

matchParentSize في Box

كما ذكرنا أعلاه، إذا كنت تريد أن يكون حجم التنسيق الفرعي هو نفسه حجم التنسيق الرئيسي Box بدون التأثير في حجم Box، استخدِم المعدِّل matchParentSize.

يُرجى العِلم أنّ matchParentSize لا تتوفّر إلا ضمن نطاق Box، ما يعني أنّها تنطبق فقط على العناصر الثانوية المباشرة لعناصر Box القابلة للإنشاء.

في المثال أدناه، يأخذ العنصر الفرعي Spacer حجمه من العنصر الرئيسي Box، الذي يأخذ حجمه بدوره من أكبر العناصر الفرعية، ArtistCard في هذه الحالة.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

خلفية رمادية تملأ الحاوية

إذا تم استخدام fillMaxSize بدلاً من matchParentSize، سيشغل Spacer كل المساحة المتاحة المسموح بها للعنصر الرئيسي، ما يؤدي بدوره إلى توسيع العنصر الرئيسي وملء كل المساحة المتاحة.

خلفية رمادية تملأ الشاشة

weight في Row وColumn

كما رأيت في القسم السابق حول المساحة المتروكة والحجم، يتم تلقائيًا تحديد حجم العنصر القابل للإنشاء حسب المحتوى الذي يلتف حوله. يمكنك ضبط حجم عنصر قابل للإنشاء ليكون مرنًا ضمن العنصر الأصل باستخدام المعدِّل weight المتوفّر فقط في RowScope وColumnScope.

لنفترض أنّ لدينا Row يحتوي على عنصرَين من عناصر Box القابلة للإنشاء. يتم منح المربع الأول ضعف weight المربع الثاني، وبالتالي يتم منحه ضعف العرض. بما أنّ عرض Row هو 210.dp، فإنّ عرض Box الأول هو 140.dp، وعرض Box الثاني هو 70.dp:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

عرض الصورة ضعف عرض النص

استخراج المعدِّلات وإعادة استخدامها

يمكن ربط معدِّلات متعددة معًا لتزيين أو زيادة وظائف عنصر قابل للإنشاء. يتم إنشاء هذه السلسلة من خلال واجهة Modifier التي تمثّل قائمة مرتبة وثابتة من Modifier.Elements واحدة.

يمثّل كل Modifier.Element سلوكًا فرديًا، مثل سلوك التنسيق والرسم والرسومات، وجميع السلوكيات المرتبطة بالإيماءات، وسلوكيات التركيز والدلالات، بالإضافة إلى أحداث إدخال البيانات على الجهاز. ويُعدّ ترتيبها مهمًا، إذ سيتم تطبيق عناصر المعدِّل التي تتم إضافتها أولاً.

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

  • لن تتم إعادة تخصيص المعدِّلات عند إعادة الإنشاء للعناصر القابلة للإنشاء التي تستخدمها
  • يمكن أن تكون سلاسل المعدِّلات طويلة ومعقّدة جدًا، لذا يمكن أن يؤدي إعادة استخدام مثيل السلسلة نفسه إلى تخفيف عبء العمل الذي يحتاج إليه وقت تشغيل Compose عند مقارنتها.
  • تعزّز عملية الاستخراج هذه نظافة الرمز البرمجي واتساقه وسهولة صيانته في جميع أنحاء قاعدة الرموز البرمجية

أفضل الممارسات لإعادة استخدام المعدّلات

أنشئ سلاسل Modifier خاصة بك واستخرِجها لإعادة استخدامها في عدة مكونات قابلة للإنشاء. لا بأس بحفظ معدِّل فقط، لأنّها عناصر تشبه البيانات:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

استخراج المعدِّلات وإعادة استخدامها عند مراقبة حالة تتغيّر بشكل متكرّر

عند مراقبة الحالات التي تتغير بشكل متكرر داخل العناصر القابلة للإنشاء، مثل حالات الرسوم المتحركة أو scrollState، يمكن أن تحدث عمليات إعادة تركيب كثيرة. في هذه الحالة، سيتم تخصيص المعدِّلات في كل عملية إعادة إنشاء وربما لكل إطار:

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

بدلاً من ذلك، يمكنك إنشاء واستخراج وإعادة استخدام مثيل المعدِّل نفسه وتمريره إلى العنصر القابل للإنشاء على النحو التالي:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

استخراج المعدِّلات غير النطاقية وإعادة استخدامها

يمكن أن تكون المعدِّلات غير محدودة النطاق أو محدودة النطاق لتطبيق عنصر قابل للإنشاء. في حال استخدام معدِّلات غير محددة النطاق، يمكنك استخراجها بسهولة خارج أي عناصر قابلة للإنشاء على شكل متغيرات بسيطة:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

ويمكن أن يكون ذلك مفيدًا بشكل خاص عند دمجه مع التصاميم الكسولة. في معظم الحالات، من المفترض أن تتضمّن جميع العناصر، التي قد يكون عددها كبيرًا، المعدّلات نفسها بالضبط:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

استخراج المُعدِّلات ذات النطاق المحدود وإعادة استخدامها

عند التعامل مع معدِّلات محددة النطاق ضمن عناصر قابلة للإنشاء، يمكنك استخراجها إلى أعلى مستوى ممكن وإعادة استخدامها عند الحاجة:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

يجب تمرير المعدِّلات المستخرَجة والمحدّدة النطاق إلى العناصر الفرعية المباشرة التي لها النطاق نفسه فقط. يمكنك الاطّلاع على القسم أمان النطاق في Compose للحصول على مزيد من المعلومات حول أهمية ذلك:

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

سلسلة إضافية من المعدِّلات المستخرَجة

يمكنك ربط سلاسل المعدّلات المستخرَجة أو إلحاقها ببعضها من خلال استدعاء الدالة .then():

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

تذكَّر فقط أنّ ترتيب المعدِّلات مهم.

مزيد من المعلومات

نقدّم قائمة كاملة بالمعدّلات، مع معلَماتها ونطاقاتها.

للحصول على مزيد من التدريب على كيفية استخدام المعدِّلات، يمكنك أيضًا الاطّلاع على الدروس التطبيقية حول الترميز الخاصة بالتصاميم الأساسية في Compose أو الرجوع إلى مستودع Now in Android.

لمزيد من المعلومات حول أدوات التعديل المخصّصة وكيفية إنشائها، يمكنك الاطّلاع على المستندات حول التنسيقات المخصّصة - استخدام أداة تعديل التنسيق.