‫ConstraintLayout در Compose

‫ConstraintLayout چیدمانی است که به شما امکان می‌دهد composableها را نسبت به composableهای دیگر در صفحه قرار دهید. این روش جایگزینی برای استفاده از چندین Row، Column، Box، و عنصر چیدمان سفارشی تودرتو است.

در سیستم View،‏ ConstraintLayout روش توصیه‌شده برای ایجاد چیدمان‌های بزرگ و پیچیده بود، زیرا سلسله‌مراتب نمای مسطح برای عملکرد بهتر از نماهای تودرتو بود. اما این موضوع در Compose نگران‌کننده نیست، زیرا Compose می‌تواند به‌طور مؤثری سلسله‌مراتب چیدمان عمیق را مدیریت کند، بنابراین ConstraintLayout چندان مفید نیست.

با ConstraintLayout شروع کنید

برای استفاده از ConstraintLayout در Compose، باید این وابستگی را در build.gradle (علاوه‌بر راه‌اندازی Compose) اضافه کنید:

implementation "androidx.constraintlayout:constraintlayout-compose:$constraintlayout_compose_version"

ConstraintLayout در Compose بااستفاده از DSL به روش زیر کار می‌کند:

  • برای هر composable در ConstraintLayout بااستفاده از createRefs() یا createRefFor() مرجع ایجاد کنید.
  • محدودیت‌ها بااستفاده از اصلاح‌کننده constrainAs() ارائه می‌شوند که مرجع را به‌عنوان پارامتر می‌گیرد و به شما امکان می‌دهد محدودیت‌های آن را در بدنه لامبدا مشخص کنید.
  • محدودیت‌ها بااستفاده از linkTo() یا روش‌های مفید دیگر مشخص می‌شوند.
  • ‫parent مرجع موجودی است که می‌توان از آن برای مشخص کردن محدودیت‌ها در خود ConstraintLayout composable استفاده کرد.

در اینجا مثالی از یک ترکیب‌پذیر بااستفاده از 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 محدود می‌کند.

دکمه بالای نوشتار نشان داده می‌شود
شکل ۱. یک Button و یک Text ترکیب‌پذیر که در یک ConstraintLayout به یکدیگر محدود شده‌اند.

Decoupled API

در مثال ConstraintLayout، محدودیت‌ها به‌صورت درون‌خطی با یک اصلاح‌کننده در ترکیب‌پذیری که به آن اعمال می‌شوند، مشخص شده‌اند. بااین‌حال، شرایطی وجود دارد که ترجیح داده می‌شود محدودیت‌ها از طرح‌بندی‌هایی که بر آن‌ها اعمال می‌شوند جدا شوند. برای مثال، ممکن است بخواهید محدودیت‌ها را براساس پیکربندی صفحه تغییر دهید، یا بین دو مجموعه محدودیت پویانمایی کنید.

برای مواردی مثل این، می‌توانید از ConstraintLayout به روش دیگری استفاده کنید:

  1. ConstraintSet را به‌عنوان پارامتر به ConstraintLayout ارسال کنید.
  2. بااستفاده از اصلاح‌کننده layoutId، مرجع‌های ایجادشده در ConstraintSet را به composableها اختصاص دهید.

@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 مختلف پیکربندی کرد که تعیین می‌کند چگونه با فضای اطراف یک composable برخورد شود، مانند:

  • ChainStyle.Spread: فضا به‌طور مساوی در سراسر همه composableها توزیع می‌شود، ازجمله فضای آزاد قبل‌از اولین composable و بعداز آخرین composable.
  • ChainStyle.SpreadInside: فضا به‌طور مساوی بین همه ترکیب‌پذیرها توزیع می‌شود، بدون اینکه فضای خالی قبل‌از اولین ترکیب‌پذیر یا بعداز آخرین ترکیب‌پذیر وجود داشته باشد.
  • ChainStyle.Packed: فضا قبل‌از اولین و بعداز آخرین ترکیب‌پذیر توزیع می‌شود، ترکیب‌پذیرها بدون فاصله بین یکدیگر در کنار هم قرار می‌گیرند.