Bố cục luồng trong Compose

FlowRowFlowColumn là các thành phần kết hợp tương tự như RowColumn, nhưng khác ở các mục đó chuyển sang dòng tiếp theo khi vùng chứa hết không gian. Thao tác này sẽ tạo nhiều hàng hoặc cột. Bạn cũng có thể kiểm soát số lượng mục trong một hàng bằng cách đặt maxItemsInEachRow hoặc maxItemsInEachColumn. Bạn thường có thể sử dụng FlowRowFlowColumn để tạo bố cục thích ứng – nội dung sẽ không bị cắt nếu các mục quá lớn cho một kích thước và sử dụng kết hợp maxItemsInEach* với Modifier.weight(weight) có thể giúp tạo bố cục lấp đầy/mở rộng chiều rộng của hàng hoặc cột khi cần.

Ví dụ điển hình là cho một khối hoặc giao diện người dùng lọc:

5 khối trong một FlowRow, cho thấy tình trạng tràn sang dòng tiếp theo khi không có khối nào
có thêm dung lượng trống.
Hình 1. Ví dụ về FlowRow

Cách sử dụng cơ bản

Để sử dụng FlowRow hoặc FlowColumn, hãy tạo các thành phần kết hợp này và đặt các mục bên trong lớp đó phải tuân theo luồng tiêu chuẩn:

@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")
    }
}

Đoạn mã này dẫn đến giao diện người dùng hiển thị ở trên, với các mục tự động chuyển đến hàng tiếp theo khi không còn chỗ trong hàng đầu tiên.

Tính năng của bố cục luồng

Bố cục flow có các tính năng và thuộc tính mà bạn có thể sử dụng để tạo nhiều bố cục khác nhau trong ứng dụng của mình.

Cách sắp xếp trục chính: sắp xếp theo chiều ngang hoặc chiều dọc

Trục chính là trục mà các mục được bố trí (ví dụ: trong FlowRow, các mục được sắp xếp theo chiều ngang). Tham số horizontalArrangement trong FlowRow kiểm soát cách phân bổ không gian trống giữa các mục.

Bảng sau đây cho thấy các ví dụ về cách đặt horizontalArrangement trên các mục cho FlowRow:

Đã đặt chế độ sắp xếp theo chiều ngang trên FlowRow

Kết quả

Arrangement.Start (Default)

Các mục được sắp xếp với phần đầu

Arrangement.SpaceBetween

Sắp xếp các mục có khoảng trống ở giữa

Arrangement.Center

Các mục được sắp xếp ở trung tâm

Arrangement.End

Các mục được sắp xếp ở cuối

Arrangement.SpaceAround

Các mục được sắp xếp với khoảng trống xung quanh

Arrangement.spacedBy(8.dp)

Các mục được giãn cách theo một dp nhất định

Đối với FlowColumn, có các lựa chọn tương tự cho verticalArrangement, với giá trị mặc định là Arrangement.Top.

Sắp xếp trên trục chéo

Trục chéo là trục ngược với trục chính. Để ví dụ: trong FlowRow, đây là trục tung. Để thay đổi cách nội dung bên trong vùng chứa được sắp xếp theo trục chéo, hãy sử dụng verticalArrangement cho FlowRowhorizontalArrangement cho FlowColumn.

Đối với FlowRow, bảng sau đây trình bày ví dụ về cách thiết lập các chế độ cài đặt khác nhau verticalArrangement cho các mặt hàng:

Chế độ sắp xếp theo chiều dọc được đặt trên FlowRow

Kết quả

Arrangement.Top (Default)

Sắp xếp trên vùng chứa

Arrangement.Bottom

Sắp xếp phần dưới cùng của vùng chứa

Arrangement.Center

Sắp xếp giữa vùng chứa

Đối với FlowColumn, bạn có thể sử dụng các lựa chọn tương tự cho horizontalArrangement. Bố cục trục chéo mặc định là Arrangement.Start.

Căn chỉnh từng mục riêng lẻ

Bạn có thể muốn đặt các mục riêng lẻ trong hàng bằng các vị trí khác nhau căn chỉnh. Điều này khác với verticalArrangementhorizontalArrangement vì chế độ này sẽ căn chỉnh các mục trong dòng hiện tại. Bạn có thể áp dụng phương thức này bằng Modifier.align().

Ví dụ: khi các mục trong FlowRow có chiều cao khác nhau, hàng này sẽ lấy giá trị chiều cao của mục lớn nhất và áp dụng Modifier.align(alignmentOption) cho mục:

Đã đặt căn chỉnh theo chiều dọc trên FlowRow

Kết quả

Alignment.Top (Default)

Các mục được căn chỉnh lên trên cùng

Alignment.Bottom

Các mục được căn chỉnh xuống dưới cùng

Alignment.CenterVertically

Các mục được căn giữa

Đối với FlowColumn, bạn có thể sử dụng các lựa chọn tương tự. Căn chỉnh mặc định là Alignment.Start.

Số mục tối đa trong hàng hoặc cột

Tham số maxItemsInEachRow hoặc maxItemsInEachColumn xác định giá trị tối đa các mục trong trục chính để cho phép trong một dòng trước khi xuống dòng tiếp theo. Chiến lược phát hành đĩa đơn mặc định là Int.MAX_INT, cho phép nhiều mục nhất có thể, miễn là kích thước của chúng cho vừa với đường kẻ.

Ví dụ: việc đặt maxItemsInEachRow sẽ buộc bố cục ban đầu chỉ có 3 mục:

Chưa đặt giới hạn tối đa

maxItemsInEachRow = 3

Không đặt giá trị tối đa trên hàng flow Đã đặt số mục tối đa trên hàng luồng

Tải từng phần các mục trong luồng

ContextualFlowRowContextualFlowColumn là sự kiện chuyên biệt phiên bản FlowRowFlowColumn cho phép bạn tải từng phần nội dung của hàng hoặc cột luồng. Các thuộc tính này cũng cung cấp thông tin về vị trí của mục (chỉ mục, số hàng và kích thước có sẵn), chẳng hạn như liệu mục có nằm trong hàng đầu tiên hay không. Điều này rất hữu ích cho các tập dữ liệu lớn và nếu bạn cần thông tin theo ngữ cảnh về một mặt hàng.

Tham số maxLines giới hạn số hàng hiển thị, còn overflow tham số chỉ định nội dung nào sẽ được hiển thị khi tràn các mục đã truy cập, cho phép bạn chỉ định expandIndicator tuỳ chỉnh hoặc collapseIndicator.

Ví dụ: để hiển thị "+ (số mục còn lại)" hoặc "Ẩn bớt" nút:

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")
}

Ví dụ về hàng luồng theo ngữ cảnh.
Hình 2. Ví dụ về ContextualFlowRow

Trọng lượng mặt hàng

Trọng số sẽ tăng trọng số của một mục dựa trên hệ số của nó và không gian có sẵn trên dòng mục đó đã được đặt ở đâu. Điều quan trọng là có sự khác biệt giữa FlowRowRow về cách sử dụng trọng số để tính chiều rộng của một mục. Đối với Rows, trọng số dựa trên tất cả các mục trong Row. Với FlowRow, trọng số được dựa trên mục trong dòng mà một mục được đặt vào, không phải tất cả các mục trong Vùng chứa FlowRow.

Ví dụ: nếu bạn có 4 mục cùng nằm trên một dòng, mỗi mục có các giá trị khác nhau trọng số là 1f, 2f, 1f3f, thì tổng trọng lượng là 7f. Dung lượng còn lại trong một hàng hoặc cột sẽ được chia cho 7f. Sau đó, chiều rộng của mỗi mục sẽ được tính bằng: weight * (remainingSpace / totalWeight).

Bạn có thể sử dụng kết hợp Modifier.weight và các mục tối đa với FlowRow hoặc FlowColumn để tạo bố cục giống như lưới. Phương pháp này rất hữu ích khi tạo bố cục thích ứng điều chỉnh theo kích thước thiết bị của bạn.

Sau đây là một số ví dụ về những gì bạn có thể đạt được khi sử dụng trọng số. Ví dụ: một lưới có các mục có kích thước bằng nhau, như minh hoạ dưới đây:

Lưới được tạo bằng hàng flow
Hình 3. Sử dụng FlowRow để tạo lưới

Để tạo lưới có kích thước mục bằng nhau, bạn có thể làm như sau:

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)
    }
}

Quan trọng là nếu bạn thêm một mục khác và lặp lại 10 lần thay vì 9 lần, mục cuối cùng chiếm toàn bộ cột cuối cùng, làm tổng trọng số của toàn bộ hàng là 1f:

Kích thước đầy đủ của mục cuối cùng trên lưới
Hình 4. Sử dụng FlowRow để tạo lưới có mục cuối cùng chiếm toàn bộ chiều rộng

Bạn có thể kết hợp trọng số với Modifiers khác, chẳng hạn như Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio) hoặc Modifier.fillMaxWidth(fraction). Tất cả các đối tượng sửa đổi này hoạt động cùng với cho phép định cỡ thích ứng các mục trong FlowRow (hoặc FlowColumn).

Bạn cũng có thể tạo một lưới xen kẽ gồm các kích thước mục khác nhau, trong đó có hai mục chiếm một nửa chiều rộng của từng mục và một mục chiếm toàn bộ chiều rộng của mục tiếp theo cột:

Lưới luân phiên với hàng flow
Hình 5. FlowRow với kích thước xen kẽ của các hàng

Bạn có thể thực hiện điều này bằng mã sau:

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))
        }
    }
}

Định cỡ phân số

Bằng cách sử dụng Modifier.fillMaxWidth(fraction), bạn có thể chỉ định kích thước của phần tử vùng chứa mà một mục sẽ chiếm dụng. Điều này khác với cách Modifier.fillMaxWidth(fraction) hoạt động khi áp dụng cho Row hoặc Column, theo các mục Row/Column chiếm tỷ lệ phần trăm chiều rộng còn lại, thay vì chiều rộng của toàn bộ vùng chứa.

Ví dụ: đoạn mã sau đây tạo ra kết quả khác khi sử dụng FlowRow so với 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: Mục ở giữa chiếm 0,7 phần chiều rộng của toàn bộ vùng chứa.

Chiều rộng phân đoạn với hàng flow

Row: Mục ở giữa chiếm 0,7% chiều rộng Row còn lại.

Chiều rộng phân số có hàng

fillMaxColumnWidth()fillMaxRowHeight()

Áp dụng Modifier.fillMaxColumnWidth() hoặc Modifier.fillMaxRowHeight() với một mục bên trong FlowColumn hoặc FlowRow đảm bảo rằng các mục trong cùng một cột hoặc hàng có cùng chiều rộng hoặc chiều cao với mục lớn nhất trong cột/hàng.

Ví dụ: ví dụ này sử dụng FlowColumn để hiển thị danh sách các món tráng miệng Android. Bạn có thể thấy sự khác biệt về chiều rộng của từng mục khi Modifier.fillMaxColumnWidth() được áp dụng cho các mục so với khi không áp dụng và các mục xuống dòng.

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)
            )
        }
    }
}

Đã áp dụng Modifier.fillMaxColumnWidth() cho mỗi mục

FillMaxColumnWidth

Chưa đặt thay đổi chiều rộng nào (gói các mục)

Chưa đặt chiều rộng cột tối đa cho màu nền