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

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

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

المعدِّلات هي عناصر 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 التي تعرضها الدالة السابقة، يؤثر التسلسل في النتيجة النهائية. لنطّلِع على مثال على ذلك:

@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

By default, layouts provided in Compose wrap their children. ومع ذلك، يمكنك ضبط حجم باستخدام المُعدِّل 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. تتبع المَعلمتان start وend في padding اتجاه التنسيق. لإضافة مساحة متروكة إلى الحافة اليمنى أو اليسرى بغض النظر عن اتجاه التنسيق، استخدِم absolutePadding مع المَعلمتَين left وright.

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

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