ConstraintLayout في Compose

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

ننصحك باستخدام ConstraintLayout في السيناريوهات التالية:

  • لتجنُّب تداخل علامات Column وRow متعددة من أجل وضع العناصر على الشاشة لتحسين إمكانية قراءة الرمز البرمجي.
  • لتحديد موضع العناصر القابلة للإنشاء مقارنةً بالعناصر الأخرى القابلة للإنشاء أو لتحديد موضعها استنادًا إلى الإرشادات أو الحواجز أو السلاسل

في نظام "العرض"، كانت ConstraintLayout هي الطريقة المُقترَحة لإنشاء تنسيقات كبيرة ومعقدة، لأنّ التسلسل الهرمي للعرض المسطّح كان أفضل من الأداء مقارنةً بالتنسيقات المُدمجة. ومع ذلك، لا داعي للقلق في أداة Compose التي يمكنها التعامل بفعالية مع التسلسلات الهرمية للتخطيط العميق.

بدء العمل باستخدام ConstraintLayout

لاستخدام ConstraintLayout في Compose، عليك إضافة هذا الاعتماد في build.gradle (بالإضافة إلى إعداد Compose):

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

تعمل ConstraintLayout في ميزة "الإنشاء" بالطريقة التالية باستخدام DSL:

  • أنشئ مراجع لكل عنصر قابل للتجميع في ConstraintLayout باستخدام رمزَي createRefs() أو createRefFor().
  • يتم تقديم القيود باستخدام المُعدِّل constrainAs() الذي يأخذ المرجع كمَعلمة ويتيح لك تحديد قيوده في العنصر lambda.
  • يتم تحديد القيود باستخدام linkTo() أو طرق أخرى مفيدة.
  • parent هو مرجع حالي يمكن استخدامه لتحديد القيود باتجاه العنصر القابل للإنشاء ConstraintLayout نفسه.

في ما يلي مثال على عنصر قابل للتجميع يستخدم ConstraintLayout:

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // Create references for the composables to constrain
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // Assign reference "button" to the Button composable
            // and constrain it to the top of the ConstraintLayout
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        // Assign reference "text" to the Text composable
        // and constrain it to the bottom of the Button composable
        Text(
            "Text",
            Modifier.constrainAs(text) {
                top.linkTo(button.bottom, margin = 16.dp)
            }
        )
    }
}

تقيِّد هذه التعليمة البرمجية الجزء العلوي من Button بالعنصر الرئيسي باستخدام هامش مقداره 16.dp وText في أسفل Button أيضًا باستخدام هامش مقداره 16.dp.

تعرِض هذه الشاشة زرًا وعنصر نص منظَّمَين في ConstraintLayout.

واجهة برمجة التطبيقات التي تم فصلها

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

في مثل هذه الحالات، يمكنك استخدام ConstraintLayout بطريقة مختلفة:

  1. نقْل ConstraintSet كمَعلمة إلى ConstraintLayout.
  2. يمكنك تخصيص المراجع التي تم إنشاؤها في ConstraintSet للعناصر القابلة للإنشاء باستخدام مفتاح التعديل layoutId.

@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button")
            ) {
                Text("Button")
            }

            Text("Text", Modifier.layoutId("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

بعد ذلك، عندما تحتاج إلى تغيير القيود، ما عليك سوى تمرير ConstraintSet مختلف.

ConstraintLayout مفاهيم

يحتوي ConstraintLayout على مفاهيم، مثل الإرشادات والحدود والسلاسل التي يمكن أن تساعد في تحديد موضع العناصر داخل العنصر القابل للتجميع.

الإرشادات

الإرشادات هي مساعدات مرئية صغيرة لتصميم التنسيقات. يمكن تقييد العناصر القابلة للإنشاء وفقًا لإرشادات. تكون الإرشادات مفيدة لوضع العناصر في dp أو percentage معيّن داخل العنصر القابل للتجميع الرئيسي.

هناك نوعان مختلفان من الإرشادات، وهما الإرشادات العمودية والأفقية. رمزَا الرمزَين الأفقيَين هما top وbottom، ورمزَا الرمزَين العموديَين هما start و end.

ConstraintLayout {
    // Create guideline from the start of the parent at 10% the width of the Composable
    val startGuideline = createGuidelineFromStart(0.1f)
    // Create guideline from the end of the parent at 10% the width of the Composable
    val endGuideline = createGuidelineFromEnd(0.1f)
    //  Create guideline from 16 dp from the top of the parent
    val topGuideline = createGuidelineFromTop(16.dp)
    //  Create guideline from 16 dp from the bottom of the parent
    val bottomGuideline = createGuidelineFromBottom(16.dp)
}

لإنشاء إرشاد، استخدِم createGuidelineFrom* مع نوع الإرشاد المطلوب. يؤدي ذلك إلى إنشاء مرجع يمكن استخدامه في العنصر Modifier.constrainAs().

حواجز

تشير الحواجز إلى عناصر قابلة للتجميع متعددة لإنشاء إرشادات افتراضية استنادًا إلى أداة العرض الأقصى على الجانب المحدّد.

لإنشاء حاجز، استخدِم createTopBarrier() (أو: createBottomBarrier()، createEndBarrier()، createStartBarrier())، وقدِّم الإشارات التي повиннаتشكل الحاجز.

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val topBarrier = createTopBarrier(button, text)
    }
}

ويمكن بعد ذلك استخدام الحاجز في كتلة Modifier.constrainAs().

سلاسل

توفّر السلاسل سلوكًا شبيهًا بالمجموعة في محور واحد (أفقيًا أو عموديًا). ويمكن تقييد المحور الآخر بشكل مستقل.

لإنشاء سلسلة، استخدِم createVerticalChain أو createHorizontalChain:

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
        val horizontalChain = createHorizontalChain(button, text)
    }
}

ويمكن بعد ذلك استخدام السلسلة في كتلة Modifier.constrainAs().

يمكن ضبط سلسلة باستخدام ChainStyles مختلفة، والتي تحدّد كيفية التعامل مع المساحة المحيطة بعنصر قابل للتجميع، مثل:

  • ChainStyle.Spread: يتم توزيع المساحة بالتساوي على جميع العناصر القابلة للتجميع، بما في ذلك المساحة الفارغة قبل العنصر القابل للتجميع الأول وبعده.
  • ChainStyle.SpreadInside: يتم توزيع المساحة بالتساوي على جميع العناصر القابلة للتجميع، بدون أي مساحة فارغة قبل العنصر القابل للتجميع الأول أو بعد العنصر القابل للتجميع الأخير.
  • ChainStyle.Packed: يتم توزيع المسافة قبل العنصر القابل للدمج الأول وبعده، ويتم تجميع العناصر القابلة للدمج معًا بدون ترك مسافة بينها.

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

تعرّف على مزيد من المعلومات حول ConstraintLayout في Compose من واجهات برمجة التطبيقات قيد التشغيل في نماذج Compose التي تستخدم ConstraintLayout.