אילוץ פריסה בכתיבה

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 ואת Text לחלק התחתון של Button, גם הוא עם שוליים של 16.dp.

הצגת לחצן ורכיב טקסט שמסודרים ב-ConstraintLayout

API מנותק

בדוגמה ConstraintLayout, האילוצים מוגדרים בשורה, עם שינוי ב-composable שאליו הם חלים. עם זאת, יש מצבים שבהם עדיף לנתק את האילוצים מהפריסות שהם חלים עליהן. לדוגמה, יכול להיות שתרצו לשנות את האילוצים על סמך הגדרת המסך, או ליצור אנימציה בין שתי קבוצות של אילוצים.

במקרים כאלה, אפשר להשתמש ב-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: הרווח מופיע לפני ה-Composable הראשון ואחרי ה-Composable האחרון, וה-Composables מקובצים יחד ללא רווח ביניהם.

מידע נוסף

מידע נוסף על ConstraintLayout ב-Compose זמין בממשקי ה-API בפעולה, בדוגמאות ל-Compose שמשתמשות ב-ConstraintLayout.