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

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

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

المعدِّلات هي كائنات 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، تقبل العناصر مَعلمة 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 الذي يأخذ تعبير لامدا. لمزيد من المعلومات المتعمّقة حول الحالات التي يجب فيها استخدام كل من هذين الخيارَين وكيفية تحسين الأداء، يُرجى قراءة قسم أداء 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، وعرض الثاني هو 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.

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