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

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

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

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

fillMaxColumnWidth

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

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