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

ConstraintLayout היא פריסה שמאפשרת למקם רכיבי Composable ביחס לרכיבי Composable אחרים במסך. הוא מהווה חלופה לשימוש בכמה רכיבים מוטמעים של Row, Column, Box ופריסות מותאמות אישית אחרות. ConstraintLayout is useful when implementing larger layouts with more complicated alignment requirements.

כדאי להשתמש ב-ConstraintLayout בתרחישים הבאים:

  • כדי להימנע מהוספת כמה תגי Column ו-Row למיקום רכיבים במסך, וכך לשפר את קריאות הקוד.
  • כדי למקם קומפוזיציות ביחס לקומפוזיציות אחרות, או כדי למקם קומפוזיציות על סמך הנחיות, מחסומים או שרשראות.

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

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

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

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

השימוש ב-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.

הלחצן מופיע מעל הטקסט
איור 1.Button ו-Text הם רכיבים שאפשר להרכיב, והם מוגבלים זה לזה ב-ConstraintLayout.

Decoupled API

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

במקרים כאלה, אפשר להשתמש ב-ConstraintLayout בדרך אחרת:

  1. מעבירים את ConstraintSet כפרמטר אל ConstraintLayout.
  2. משתמשים בשינוי layoutId כדי להקצות הפניות שנוצרו ב-ConstraintSet לרכיבים הניתנים להרכבה.

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

הנחיות

ההנחיות הן עזרים חזותיים קטנים לעיצוב פריסות. אפשר להגביל את השימוש ברכיבי Composables להנחיה מסוימת. הנחיות שימושיות למיקום רכיבים ב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().

מחסומים

Barriers מפנה לכמה רכיבים שאפשר להרכבה כדי ליצור קו הנחיה וירטואלי שמבוסס על הווידג'ט הקיצוני ביותר בצד שצוין.

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

מידע נוסף

במאמר הזה יש דוגמאות לשימוש ב-ConstraintLayout בכתיבה, שממחישות את השימוש בממשקי ה-API.ConstraintLayout