เลย์เอาต์โฟลว์ใน Compose

FlowRow และ FlowColumn เป็นคอมโพสิเบิลที่คล้ายกับ Row และ Column แต่แตกต่างกันตรงที่รายการจะไหลไปยังบรรทัดถัดไปเมื่อคอนเทนเนอร์มีพื้นที่ไม่เพียงพอ ซึ่งจะสร้างแถวหรือคอลัมน์หลายรายการ คุณยังควบคุมจำนวนรายการในบรรทัดได้ด้วยการตั้งค่า maxItemsInEachRow หรือ maxItemsInEachColumn คุณมักใช้ FlowRow และ FlowColumn เพื่อสร้างเลย์เอาต์ที่ตอบสนองได้ เนื้อหาจะไม่ถูกตัดออกหากรายการมีขนาดใหญ่เกินมิติข้อมูลเดียว และการใช้ maxItemsInEach* ร่วมกับ Modifier.weight(weight) จะช่วยสร้างเลย์เอาต์ที่เติม/ขยายความกว้างของแถวหรือคอลัมน์ได้เมื่อจำเป็น

ตัวอย่างทั่วไปสำหรับชิปหรือ UI การกรอง

ชิป 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")
    }
}

ตัวอย่างข้อมูลนี้แสดง UI ดังที่แสดงด้านบน โดยระบบจะย้ายรายการไปยังแถวถัดไปโดยอัตโนมัติเมื่อแถวแรกไม่มีพื้นที่เหลือ

ฟีเจอร์ของเลย์เอาต์แบบโฟลว์

เลย์เอาต์แบบโฟลว์มีฟีเจอร์และพร็อพเพอร์ตี้ต่อไปนี้ที่คุณสามารถใช้เพื่อสร้างเลย์เอาต์ต่างๆ ในแอป

การจัดเรียงแกนหลัก: การจัดเรียงแนวนอนหรือแนวตั้ง

กําหนดแกนหลักเป็นแกนที่ใช้วางเลย์เอาต์รายการ (เช่น ใน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

ไม่มีการตั้งค่าสูงสุดในแถวการไหล รายการสูงสุดที่ตั้งค่าไว้ในแถวขั้นตอน

รายการโฟลว์การโหลดแบบ Lazy Loading

ContextualFlowRow และ ContextualFlowColumn เป็น FlowRow และ FlowColumn เวอร์ชันเฉพาะที่ช่วยให้คุณโหลดเนื้อหาของแถวหรือคอลัมน์ของโฟลว์แบบ Lazy Load ได้ รวมถึงให้ข้อมูลเกี่ยวกับตําแหน่งของสินค้า (ดัชนี หมายเลขแถว และขนาดที่ใช้ได้) เช่น ในกรณีที่สินค้าอยู่ในแถวแรก ซึ่งมีประโยชน์สำหรับชุดข้อมูลขนาดใหญ่และในกรณีที่คุณต้องการข้อมูลตามบริบทเกี่ยวกับรายการ

พารามิเตอร์ 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) ให้ปรับเปลี่ยนตามอุปกรณ์

นอกจากนี้ คุณยังสร้างตารางกริดสลับขนาดของรายการต่างๆ ได้โดยให้ 2 รายการใช้พื้นที่ครึ่งหนึ่งของความกว้างแต่ละรายการ และ 1 รายการใช้พื้นที่ความกว้างทั้งหมดของคอลัมน์ถัดไป ดังนี้

ตารางกริดสลับกับแถวลำดับ
รูปที่ 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 desserts คุณจะเห็นความแตกต่างของขนาดความกว้างของรายการแต่ละรายการเมื่อใช้ 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

ไม่มีการตั้งค่าการเปลี่ยนแปลงความกว้าง (การตัดรายการ)

ไม่ได้ตั้งค่าความกว้างคอลัมน์สูงสุดเพื่อกรอกข้อมูล