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 ở chỗ các mục sẽ chuyển sang dòng tiếp theo khi vùng chứa hết dung lượng. 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 dòng bằng cách đặt maxItemsInEachRow hoặc maxItemsInEachColumn. Bạn thường có thể 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 đối với một phương diện và việc 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 một hàng hoặc cột khi cần.

Ví dụ điển hình là cho một thành phần hiển thị dạng chip hoặc giao diện người dùng lọc:

5 thành phần hiển thị trong một FlowRow, cho thấy phần tràn sang dòng tiếp theo khi không còn chỗ 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 thành phần kết hợp đó theo quy trình 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 tạo ra giao diện người dùng như trên, với các mục tự động chuyển sang hàng tiếp theo khi không còn chỗ trống ở hàng đầu tiên.

Các tính năng của bố cục luồng

Bố cục dạng dòng có các tính năng và thuộc tính sau đây mà bạn có thể dùng để tạo nhiều bố cục trong ứng dụng.

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 phối khoảng trống giữa các mục.

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

Đã đặt bố cục ngang thành FlowRow

Kết quả

Arrangement.Start (Default)

Các mục được sắp xếp theo thứ tự bắt đầu

Arrangement.SpaceBetween

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

Arrangement.Center

Các mục được sắp xếp ở giữa

Arrangement.End

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

Arrangement.SpaceAround

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

Arrangement.spacedBy(8.dp)

Các mục được phân cách bằng một dp nhất định

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

Sắp xếp theo trục chéo

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

Đối với FlowRow, bảng sau đây cho thấy ví dụ về cách đặt verticalArrangement khác nhau cho các mục:

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

Kết quả

Arrangement.Top (Default)

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

Arrangement.Bottom

Cách bố trí phần dưới cùng của vùng chứa

Arrangement.Center

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

Đối với FlowColumn, bạn có thể dùng các lựa chọn tương tự với horizontalArrangement. Cách sắp xếp mặc định theo trục chéo 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 với các chế độ căn chỉnh khác nhau. Chế độ này khác với verticalArrangementhorizontalArrangement vì chế độ này căn chỉnh các mục trong dòng hiện tại. Bạn có thể áp dụng chế độ này bằng Modifier.align().

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

Đã đặt chế độ 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 chỉnh vào chính giữa

Đối với FlowColumn, bạn có thể 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

Các tham số maxItemsInEachRow hoặc maxItemsInEachColumn xác định số lượng tối đa các mục trong trục chính được phép xuất hiện trên một dòng trước khi chuyển sang dòng tiếp theo. Giá trị 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 các mục cho phép chúng vừa với dòng.

Ví dụ: việc đặt maxItemsInEachRow 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 có giới hạn tối đa cho hàng luồng Số lượng mục tối đa được đặt trên hàng trong luồng

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

Trọng số tăng một mục dựa trên hệ số và không gian có sẵn trên dòng mà mục đó được đặt. Đ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 lượng dựa trên tất cả các mặt hàng trong Row. Với FlowRow, trọng số dựa trên các mục trong dòng mà một mục được đặt vào, chứ 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 đều nằm trên một dòng, mỗi mục có trọng số khác nhau là 1f, 2f, 1f3f, thì tổng trọng số là 7f. Khoảng trố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 cách sử dụng: weight * (remainingSpace / totalWeight).

Bạn có thể dùng kết hợp Modifier.weight và số lượng 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 hữu ích khi tạo bố cục thích ứng điều chỉnh theo kích thước của thiết bị.

Có một số ví dụ về những gì bạn có thể đạt được khi sử dụng trọng số. Một ví dụ là 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 trong luồng
Hình 2. Sử dụng FlowRow để tạo lưới

Để tạo một lưới có kích thước cá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)
    }
}

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

Mục cuối cùng có kích thước đầy đủ trên lưới
Hình 3. Sử dụng FlowRow để tạo một 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 các 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 đều hoạt động cùng nhau để cho phép thay đổi kích thước linh hoạt của 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 nhiều kích thước mục, trong đó hai mục chiếm mỗi mục một nửa chiều rộng và một mục chiếm toàn bộ chiều rộng của cột tiếp theo:

Lưới xen kẽ với hàng linh hoạt
Hình 4. FlowRow với kích thước xen kẽ của các hàng

Bạn có thể thực hiện việc 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))
        }
    }
}

Kích thước phân số

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

Ví dụ: mã sau đây tạo ra các kết quả khác nhau 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 có 0,7 phần chiều rộng của toàn bộ vùng chứa.

Chiều rộng phân số có hàng theo dòng chảy

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ố theo hàng

fillMaxColumnWidth()fillMaxRowHeight()

Việc áp dụng Modifier.fillMaxColumnWidth() hoặc Modifier.fillMaxRowHeight() cho 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 trên 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 sẽ 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)
            )
        }
    }
}

Modifier.fillMaxColumnWidth() được áp dụng cho từng mặt hàng

fillMaxColumnWidth

Không đặt thay đổi về chiều rộng (các mục bao bọc)

Không đặt chiều rộng tối đa cho cột