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

‫ConstraintLayout הוא פריסה שמאפשרת למקם רכיבים קומפוזביליים ביחס לרכיבים קומפוזביליים אחרים במסך. הוא מהווה חלופה לשימוש במספר רכיבים מקוננים של Row, Column, Box ורכיבי פריסה אחרים בהתאמה אישית.

במערכת View, ‏ ConstraintLayout הייתה הדרך המומלצת ליצור פריסות גדולות ומורכבות, כי היררכיית תצוגות שטוחה הייתה טובה יותר לביצועים מאשר תצוגות בתצוגות. עם זאת, ב-Compose אין בעיה כזו, כי הוא יכול לטפל ביעילות בהיררכיות פריסה עמוקות, ולכן ConstraintLayout לא מועיל באותה מידה.

רוצה להתחיל לצפות בתכנים של ConstraintLayout?

כדי להשתמש ב-ConstraintLayout ב-Compose, צריך להוסיף את התלות הזו בקובץ build.gradle (בנוסף להגדרת Compose):

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

ConstraintLayout בפיתוח נייטיב פועל באופן הבא באמצעות DSL:

  • יוצרים הפניות לכל רכיב Composable ב-ConstraintLayout באמצעות createRefs() או createRefFor().
  • האילוצים מסופקים באמצעות המשתנה constrainAs(), שמקבל את ההפניה כפרמטר ומאפשר לציין את האילוצים שלה בגוף הלמבדה.
  • מגדירים את האילוצים באמצעות linkTo() או שיטות מועילות אחרות.
  • ‫parent הוא קובץ עזר קיים שאפשר להשתמש בו כדי לציין אילוצים לגבי ה-composable של 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.

הכפתור מופיע מעל הטקסט
איור 1. רכיב קומפוזבילי מסוג Button ורכיב קומפוזבילי מסוג Text מוגבלים זה לזה ב-ConstraintLayout.

‫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 מסוימים בתוך הרכיב הניתן ליצירה (composable) ההורה.

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