التنسيقات المخصصة

في Compose، يتم تمثيل عناصر في واجهة المستخدم من خلال الدوال المركّبة التي تُصدر جزءًا من واجهة المستخدم عند استدعائها، ثم تتم إضافتها إلى شجرة واجهة المستخدم التي يتم عرضها على الشاشة. يحتوي كل عنصر في واجهة المستخدم على عنصر رئيسي واحد والعديد من العناصر الفرعية المحتملة. يقع كل عنصر أيضًا داخل العنصر الرئيسي، ويتم تحديده كموضع (س، ص)، وحجم، ويتم تحديده كـ width وheight.

يحدد الآباء القيود لعناصرهم الفرعية. يُطلب من العنصر تحديد حجمه ضمن هذه القيود. تحدّ القيود من الحدّ الأدنى والحدّ الأقصى لـ width وheight للعنصر. إذا كان العنصر يحتوي على عناصر فرعية، فقد يقيس كل عنصر فرعي للمساعدة في تحديد حجمه. بعد أن يحدّد العنصر حجمه ويبلّغ عنه، يمكنه تحديد كيفية وضع العناصر الثانوية بالنسبة إليه، كما هو موضّح بالتفصيل في مقالة إنشاء تنسيقات مخصّصة.

يتم تخطيط كل عقدة في شجرة واجهة المستخدم من خلال عملية تتألف من ثلاث خطوات. يجب أن يستوفي كل عقدة ما يلي:

  1. قياس أي أطفال
  2. تحديد حجمها الخاص
  3. وضع العناصر الثانوية
الخطوات الثلاث لتخطيط العقدة: قياس العناصر الفرعية، وتحديد الحجم، ووضع العناصر الفرعية
الشكل 1: تتمثّل الخطوات الثلاث لتخطيط العقدة في قياس العناصر الفرعية وتحديد الحجم ووضع العناصر الفرعية.

يحدّد استخدام النطاقات متى يمكنك قياس العناصر الفرعية ووضعها. لا يمكن قياس التنسيق إلا أثناء مرحلتَي القياس والتنسيق، ولا يمكن وضع عنصر فرعي إلا أثناء مرحلة التنسيق (وبعد قياسه فقط). بسبب نطاقات Compose، مثل MeasureScope وPlacementScope، يتم فرض ذلك في وقت التجميع.

استخدام معدِّل التنسيق

يمكنك استخدام المُعدِّل layout لتعديل كيفية قياس العنصر وتخطيطه. ‫Layout هي دالة lambda، وتتضمّن مَعلماتها العنصر الذي يمكنك قياسه، والذي يتم تمريره على أنّه measurable، والقيود الواردة للدالة المركّبة، والتي يتم تمريرها على أنّها constraints. يمكن أن يظهر معدِّل التنسيق المخصّص على النحو التالي:

fun Modifier.customLayoutModifier() =
    layout { measurable, constraints ->
        // ...
    }

عرض Text على الشاشة والتحكّم في المسافة من الأعلى إلى خط الأساس للسطر الأول من النص هذا هو بالضبط ما يفعله المُعدِّل paddingFromBaseline، وأنت تنفّذه هنا كمثال. لإجراء ذلك، استخدِم المُعدِّل layout لوضع الدالة المركّبة يدويًا على الشاشة. في ما يلي السلوك الناتج عن ضبط المساحة المتروكة في أعلى Text على 24.dp:

صورة تعرض الفرق بين حشو واجهة المستخدم العادي، الذي يضبط المسافة بين العناصر، وحشو النص الذي يضبط المسافة من خط أساس إلى آخر
الشكل 2. تم تطبيق النص مع paddingFromBaseline.

في ما يلي الرمز البرمجي لإنشاء هذه المسافة:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }
}

إليك ما يحدث في هذه التعليمة البرمجية:

  1. في مَعلمة lambda‏ measurable، يمكنك قياس Text الذي تمثّله المَعلمة القابلة للقياس من خلال استدعاء measurable.measure(constraints).
  2. يمكنك تحديد حجم الدالة المركّبة من خلال استدعاء الطريقة layout(width, height)، والتي توفر أيضًا دالة lambda المستخدمة لوضع العناصر الملتفة. في هذه الحالة، هو الارتفاع بين خط الأساس الأخير والحشو العلوي المضاف.
  3. يمكنك تحديد موضع العناصر الملتفّة على الشاشة من خلال استدعاء placeable.place(x, y). إذا لم يتم وضع العناصر الملتفة، فلن تكون مرئية. يتوافق الموضع y مع المساحة المتروكة العلوية: موضع خط الأساس الأول للنص.

للتأكّد من أنّ هذا التعديل يعمل على النحو المتوقّع، استخدِمه على Text:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

معاينات متعددة لعناصر نصية؛ يعرض أحدهما حشوًا عاديًا بين العناصر، والآخر يعرض حشوًا من خط أساسي إلى آخر.
الشكل 3 معدِّل تم تطبيقه على دالة مركّبة Text ومعاينته

إنشاء تنسيقات مخصّصة

يغيّر المُعدِّل layout الدالة المركّبة التي تستدعيه فقط. لقياس وتخطيط عدة دوال مركّبة، استخدِم الدالة المركّبة Layout بدلاً من ذلك. تتيح لك هذه الدالة المركّبة قياس العناصر الفرعية وتنسيقها يدويًا. يتم إنشاء جميع التنسيقات ذات المستوى الأعلى، مثل Column وRow، باستخدام الدالة المركّبة Layout.

ينشئ هذا المثال نسخة أساسية جدًا من Column. تتبع معظم التنسيقات المخصصة هذا النمط:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
        // ...
    }
}

على غرار المُعدِّل layout، تمثّل measurables قائمة العناصر الثانوية التي يجب قياسها، بينما تمثّل constraints القيود من العنصر الرئيسي. باتّباع المنطق نفسه كما في السابق، يمكن تنفيذ MyBasicColumn على النحو التالي:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

تتقيّد الدوال المركّبة الفرعية بقيود Layout (بدون قيود minHeight)، ويتم وضعها استنادًا إلى yPosition الدالة المركّبة السابقة.

في ما يلي كيفية استخدام هذه الدالة المركّبة المخصّصة:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text("MyBasicColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

عدة عناصر نصية مكدسة فوق بعضها البعض في عمود.
الشكل 4. تنفيذ مخصّص لـ "Column".

اتجاه التنسيق

يمكنك تغيير اتجاه تنسيق دالة مركّبة من خلال تغيير المكوّن المحلي LocalLayoutDirection.

إذا كنت تضع الدوال المركّبة يدويًا على الشاشة، يكون LayoutDirection جزءًا من LayoutScope في المُعدِّل layout أو الدالة المركّبة Layout.

عند استخدام layoutDirection، يمكنك وضع الدوال المركّبة باستخدام place. على عكس طريقة placeRelative، لا يتغيّر place استنادًا إلى اتجاه التنسيق (من اليسار إلى اليمين مقابل من اليمين إلى اليسار).

التنسيقات المخصّصة قيد الاستخدام

يمكنك الاطّلاع على مزيد من المعلومات حول التنسيقات والمعدِّلات في التنسيقات الأساسية في Compose، والاطّلاع على التنسيقات المخصّصة أثناء العمل في نماذج Compose التي تنشئ تنسيقات مخصّصة.

مزيد من المعلومات

لمزيد من المعلومات حول التنسيقات المخصّصة في Compose، يُرجى الاطّلاع على المراجع الإضافية التالية.

الفيديوهات