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

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

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

المعدِّلات هي عناصر 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 Modifier المتاح فقط في 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
    )
}

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

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.

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