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

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

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

المُعدِّلات هي عناصر 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، والثاني هو 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.

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