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

FlowRow ו-FlowColumn הם תכנים קומפוזביליים שדומים ל-Row ול-Column, אבל יש ביניהם הבדלים מבחינת הפריטים יועברו לשורה הבאה כשנגמר המקום בקונטיינר. כך נוצרות כמה שורות או עמודות. אפשר גם לקבוע את מספר הפריטים בשורה באמצעות ההגדרה maxItemsInEachRow או maxItemsInEachColumn. בדרך כלל אפשר להשתמש ב-FlowRow וב-FlowColumn כדי ליצור פריסות עם תמיכה בהתאמה לעומס (responsive). התוכן לא ייחתך אם הפריטים גדולים מדי למאפיין אחד, ושימוש בשילוב של 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.

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

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

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

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