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 في Compose بالطريقة التالية باستخدام لغة خاصة بالمجال (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، كما يقيد الجزء السفلي من Button بالجزء السفلي من Text مع هامش يبلغ 16.dp.

تعرض هذه السمة زرًا وعنصرًا نصيًا مرتّبَين في ConstraintLayout

Decoupled API

في مثال 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 على مفاهيم مثل الإرشادات والحواجز والسلاسل التي يمكن أن تساعد في تحديد موضع العناصر داخل Composable.

الإرشادات

الإرشادات هي أدوات مساعدة بصرية صغيرة لتصميم التنسيقات. يمكن ربط العناصر القابلة للإنشاء بإرشادات. تفيد الخطوط الإرشادية في تحديد موضع العناصر عند 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.