ConstraintLayout في Compose

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

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

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

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

بدء العمل باستخدام 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 في ميزة "الكتابة الذكية" من خلال واجهات برمجة التطبيقات في ملف عيّنات "الكتابة الذكية" التي تستخدِم ConstraintLayout.