פריסות זרימה ב'כתיבה'

הפקודה FlowRow והפקודה FlowColumn הן תכנים קומפוזביליים שדומים ל-Row ול-Column, אבל שונים בפריטים האלה יועברו לשורה הבאה כשנגמר המקום בקונטיינר. הפעולה הזו יוצרת כמה שורות או עמודות. אפשר גם לשלוט במספר הפריטים בשורה על ידי הגדרה של maxItemsInEachRow או maxItemsInEachColumn. לעיתים קרובות ניתן להשתמש FlowRow ו-FlowColumn כדי לבנות פריסות רספונסיביות – התוכן לא ייחתך מושבתת אם פריטים גדולים מדי למאפיין מסוים, ומשתמשת בשילוב של maxItemsInEach* עם Modifier.weight(weight) יכול לעזור ליצור פריסות למלא או להרחיב את הרוחב של שורה או עמודה לפי הצורך.

דוגמה אופיינית היא לצ'יפ או לממשק משתמש של סינון:

5 צ'יפים בשורת זרימה, שמציגים את האפשרויות הנוספות לשורה הבאה כשאין יותר צ'יפים
לפנות יותר מקום.
איור 1. דוגמה של FlowRow

שימוש בסיסי

כדי להשתמש ב-FlowRow או ב-FlowColumn, צריך ליצור את התכנים הקומפוזביליים האלה ולהציב אותם בתוכו, שאמור לפעול לפי התהליך הרגיל:

@Composable
private fun FlowRowSimpleUsageExample() {
    FlowRow(modifier = Modifier.padding(8.dp)) {
        ChipItem("Price: High to Low")
        ChipItem("Avg rating: 4+")
        ChipItem("Free breakfast")
        ChipItem("Free cancellation")
        ChipItem("£50 pn")
    }
}

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

תכונות של פריסת הזרימה

פריסות הזרימה כוללות את התכונות והמאפיינים הבאים שניתן להשתמש בהם כדי ליצור פריסות שונות באפליקציה.

סידור הציר הראשי: סידור אופקי או אנכי

הציר הראשי הוא הציר שבו מוצגים הפריטים (לדוגמה, FlowRow, הפריטים מסודרים לרוחב). horizontalArrangement ב-FlowRow קובע את אופן חלוקת השטח הפנוי בין פריטים.

בטבלה הבאה מוצגות דוגמאות להגדרה של horizontalArrangement בפריטים עבור FlowRow:

סידור אופקי הוגדר בתאריך FlowRow

התוצאה

Arrangement.Start (Default)

הפריטים מסודרים לפי תאריך ההתחלה

Arrangement.SpaceBetween

פריטים עם רווח ביניהם

Arrangement.Center

פריטים מסודרים במרכז

Arrangement.End

הפריטים מסודרים בסוף

Arrangement.SpaceAround

פריטים מסודרים עם רווח סביבם

Arrangement.spacedBy(8.dp)

רווח בין פריטים ב-dp מסוים

ל-FlowColumn, אפשרויות דומות זמינות אצל verticalArrangement, עם ברירת המחדל היא Arrangement.Top.

סידור בצירים צולב

ציר הצלב הוא הציר ההפוך לציר הראשי. עבור לדוגמה, ב-FlowRow, זהו הציר האנכי. כדי לשנות את האופן שבו התוכן שבתוך המכל מסודר בציר הצלב, כאשר משתמשים verticalArrangement עבור FlowRow, ו-horizontalArrangement עבור FlowColumn

בטבלה הבאה מוצגות דוגמאות להגדרות של FlowRow verticalArrangement בפריטים:

הסידור האנכי הוגדר בתאריך FlowRow

תוצאה

Arrangement.Top (Default)

סידור קונטיינרים

Arrangement.Bottom

סידור הקונטיינר התחתון

Arrangement.Center

סידור מרכזי קונטיינרים

ל-FlowColumn, אפשרויות דומות זמינות אצל horizontalArrangement. סידור ברירת המחדל של הצירים בין הצירים הוא Arrangement.Start.

התאמה של פריט בודד

כדאי למקם פריטים בודדים בשורה עם והתאמתם. שונה מ-verticalArrangement ו- horizontalArrangement ליישור פריטים בשורה הנוכחית. אפשר אני רוצה להחיל את ההגדרה הזו ביחד עם Modifier.align().

לדוגמה, כשפריטים בFlowRow הם בגבהים שונים, השורה מתייחסת גובה הפריט הגדול ביותר ומחיל Modifier.align(alignmentOption) על פריטים:

יישור אנכי הוגדר בטווח FlowRow

תוצאה

Alignment.Top (Default)

פריטים שמיושרים למעלה

Alignment.Bottom

פריטים שמיושרים למטה

Alignment.CenterVertically

פריטים שמיושרים למרכז

יש אפשרויות דומות לגבי FlowColumn. יישור ברירת המחדל הוא Alignment.Start

המספר המקסימלי של פריטים בשורה או בעמודה

הפרמטרים maxItemsInEachRow או maxItemsInEachColumn מגדירים את הערך המקסימלי בציר הראשי לאפשר הוספה של פריטים בשורה אחת לפני גלישת הטקסט לשורה הבאה. ברירת המחדל היא Int.MAX_INT, ועכשיו אפשר להשתמש בכמות גדולה ככל האפשר של פריטים, כל עוד הגדלים מאפשרים להם להתאים לגודל.

לדוגמה, הגדרה של maxItemsInEachRow תאלץ את הפריסה הראשונית יש 3 פריטים:

לא הוגדר מספר מקסימלי

maxItemsInEachRow = 3

לא הוגדר מספר מקסימלי בשורת הזרימה הוגדר מספר מקסימלי של פריטים בשורת התהליך

פריטים בתהליך טעינה מדורגת

הדומיינים ContextualFlowRow ו-ContextualFlowColumn הם ספציפיים גרסה של FlowRow ו-FlowColumn שמאפשרות לבצע טעינה מדורגת של התוכן של שורת הזרימה או העמודה. הם גם מספקים מידע לגבי מיקום הפריטים (אינדקס, מספר שורה והגודל הזמין), למשל אם הפריט נמצא השורה הראשונה. האפשרות הזו שימושית למערכי נתונים גדולים ולשימוש במידע הקשרי לגבי פריט מסוים.

הפרמטר maxLines מגביל את מספר השורות המוצגות, והמשתנה overflow מציין מה צריך להציג כאשר יש שפע של פריטים שמאפשר לך לציין expandIndicator או ערך מותאם אישית collapseIndicator

לדוגמה, כדי להציג את הסימן '+ (מספר הפריטים שנותרו)' או 'הצגת פחות פרטים' לחצן:

val totalCount = 40
var maxLines by remember {
    mutableStateOf(2)
}

val moreOrCollapseIndicator = @Composable { scope: ContextualFlowRowOverflowScope ->
    val remainingItems = totalCount - scope.shownItemCount
    ChipItem(if (remainingItems == 0) "Less" else "+$remainingItems", onClick = {
        if (remainingItems == 0) {
            maxLines = 2
        } else {
            maxLines += 5
        }
    })
}
ContextualFlowRow(
    modifier = Modifier
        .safeDrawingPadding()
        .fillMaxWidth(1f)
        .padding(16.dp)
        .wrapContentHeight(align = Alignment.Top)
        .verticalScroll(rememberScrollState()),
    verticalArrangement = Arrangement.spacedBy(4.dp),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    maxLines = maxLines,
    overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
        minRowsToShowCollapse = 4,
        expandIndicator = moreOrCollapseIndicator,
        collapseIndicator = moreOrCollapseIndicator
    ),
    itemCount = totalCount
) { index ->
    ChipItem("Item $index")
}

דוגמה לשורות זרימה לפי הקשר.
איור 2. דוגמה של ContextualFlowRow

משקל הפריטים

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

לדוגמה, אם יש 4 פריטים שנמצאים בשורה אחת, כל אחד מהם משקולות של 1f, 2f, 1f ו-3f, המשקל הכולל הוא 7f. המרחב המשותף שנותר בשורה או בעמודה, יחולקו 7f. לאחר מכן, כל רוחב פריט יהיה מחושב באמצעות: weight * (remainingSpace / totalWeight).

אפשר להשתמש בשילוב של Modifier.weight ופריטים נוספים עם FlowRow או FlowColumn כדי ליצור פריסה דמוית רשת. הגישה הזו שימושית ליצירת פריסות רספונסיביות שמתאימות את הגודל של המכשיר שלכם.

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

נוצרה רשת עם שורת זרימה
איור 3. שימוש ב-FlowRow ליצירת רשת

כדי ליצור רשת עם גדלים שווים של פריטים, אפשר לבצע את הפעולות הבאות:

val rows = 3
val columns = 3
FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = rows
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .weight(1f)
        .clip(RoundedCornerShape(8.dp))
        .background(MaterialColors.Blue200)
    repeat(rows * columns) {
        Spacer(modifier = itemModifier)
    }
}

חשוב לציין שאם מוסיפים עוד פריט וחוזרים עליו 10 פעמים במקום 9, הפריט האחרון תופס את כל העמודה האחרונה, כלומר המשקל הכולל של השורה כולה היא 1f:

הפריט האחרון בגודל מלא ברשת
איור 4. שימוש ב-FlowRow ליצירת רשת שבה הפריט האחרון תופס ברוחב מלא

אפשר לשלב משקולות עם Modifiers אחרים, כמו Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio), או Modifier.fillMaxWidth(fraction). ההתאמה האישית של התנאים האלה פועלת יחד לאפשר שימוש במידות רספונסיביות של פריטים בתוך FlowRow (או FlowColumn).

אפשר גם ליצור רשת מתחלפת של גדלים שונים של פריטים, שבה יש שני פריטים תופסות חצי מהרוחב כל אחד, ופריט אחד תופס את הרוחב המלא של הפריט הבא עמודה:

רשת מתחלפת עם שורת זרימה
איור 5. FlowRow עם שורות בגדלים מתחלפים

אפשר לעשות זאת באמצעות הקוד הבא:

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 2
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .clip(RoundedCornerShape(8.dp))
        .background(Color.Blue)
    repeat(6) { item ->
        // if the item is the third item, don't use weight modifier, but rather fillMaxWidth
        if ((item + 1) % 3 == 0) {
            Spacer(modifier = itemModifier.fillMaxWidth())
        } else {
            Spacer(modifier = itemModifier.weight(0.5f))
        }
    }
}

גודל חלקי

באמצעות Modifier.fillMaxWidth(fraction), אפשר לציין את הגודל של מאגר שהפריט אמור לכלול. ההבדל בין Modifier.fillMaxWidth(fraction) פועל כשמחילים אותו על Row או Column, במדינות ש-Row/Column פריטים תופסים אחוז מהרוחב שנותר, במקום לכל רוחב הקונטיינר.

לדוגמה, הקוד הבא מפיק תוצאות שונות כשמשתמשים בפונקציה FlowRow לעומת Row:

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 3
) {
    val itemModifier = Modifier
        .clip(RoundedCornerShape(8.dp))
    Box(modifier = itemModifier.height(200.dp).width(60.dp).background(Color.Red))
    Box(modifier = itemModifier.height(200.dp).fillMaxWidth(0.7f).background(Color.Blue))
    Box(modifier = itemModifier.height(200.dp).weight(1f).background(Color.Magenta))
}

FlowRow: פריט אמצעי עם חלק של 0.7 מרוחב המאגר כולו.

רוחב חלקי עם שורת זרימה

Row: פריט אמצעי שתופס 0.7 אחוז מהרוחב שנותר של Row.

רוחב חלקי עם שורה

fillMaxColumnWidth() וגם fillMaxRowHeight()

המערכת מחילה את Modifier.fillMaxColumnWidth() או Modifier.fillMaxRowHeight() לפריט בתוך FlowColumn או FlowRow מבטיחה שהפריטים באותה עמודה או באותה שורה יהיו באותו רוחב או גובה כמו הפריט הגדול ביותר בעמודה/בשורה.

לדוגמה, בדוגמה הזו נעשה שימוש ב-FlowColumn כדי להציג את הרשימה של Android קינוחים. אפשר לראות את ההבדל בין רוחב הפריטים כאשר Modifier.fillMaxColumnWidth() הוחל על הפריטים לעומת כאשר לא, וגם שהפריטים עולים.

FlowColumn(
    Modifier
        .padding(20.dp)
        .fillMaxHeight()
        .fillMaxWidth(),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp),
    maxItemsInEachColumn = 5,
) {
    repeat(listDesserts.size) {
        Box(
            Modifier
                .fillMaxColumnWidth()
                .border(1.dp, Color.DarkGray, RoundedCornerShape(8.dp))
                .padding(8.dp)
        ) {

            Text(
                text = listDesserts[it],
                fontSize = 18.sp,
                modifier = Modifier.padding(3.dp)
            )
        }
    }
}

Modifier.fillMaxColumnWidth() הוחלו על כל פריט

fullMaxColumnwidth

לא הוגדרו שינויי רוחב (פריטי גלישה)

לא הוגדר רוחב עמודה מקסימלי למילוי