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

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

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

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

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

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

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

يمكنك استخدام المُعدِّل 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 تُستخدم لوضع العناصر الملتفة. في هذه الحالة، الارتفاع هو ارتفاع Text بالإضافة إلى placeableY، وهو firstBaselineToTop ناقص خط الأساس الأول لـ Text.
  3. يمكنك تحديد موضع العناصر الملتفّة على الشاشة من خلال استدعاء placeable.placeRelative(x, y). إذا لم يتم وضع العناصر الملتفة، فلن تكون مرئية. في التنسيق من اليمين إلى اليسار، يعكس placeRelative موضع x، راجِع اتجاه التنسيق. موضع "y" هو "firstBaselineToTop" ناقص خط الأساس الأول للنص، بحيث يكون خط الأساس الأول هو "firstBaselineToTop" من الأعلى.

للتأكّد من أنّ هذا التعديل يعمل على النحو المتوقّع، استخدِمه على 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 الواردة، ويتم وضعها استنادًا إلى 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 composition local.

إذا كنت تضع الدوال المركّبة يدويًا على الشاشة، ستكون السمة LayoutDirection متاحة من خلال السمة layoutDirection الخاصة بالسمة MeasureScope في المُعدِّل layout أو الدالة المركّبة Layout.

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

التنسيقات المخصّصة أثناء العمل

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

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

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

الفيديوهات