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

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

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

المعدِّلات هي عناصر 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 الموجب إلى نقل العنصر إلى اليمين، بينما يؤدي 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، والثاني هو 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
    )
}

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

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

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

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

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

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.

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