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

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

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

المُعدِّلات هي عناصر 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، Elements accept and respect a Modifier parameter.

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

إنّ ترتيب دوالّ المُعدِّلات مهم. بما أنّ كل دالة تُجري تغييرات على 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

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

أمان النطاق في ميزة "الإنشاء"

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

على سبيل المثال، إذا كنت تريد جعل الطفل بحجم العنصر 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
        // ...
    )
    // ...
}

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

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 أو الرجوع إلى الآن في مستودع Android.

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