Bố cục tuỳ chỉnh

Trong Compose, các thành phần trên giao diện người dùng được biểu thị bằng các hàm có khả năng kết hợp cung cấp một phần giao diện người dùng khi được gọi, sau đó được thêm vào cây giao diện người dùng hiển thị trên màn hình. Mỗi thành phần trên giao diện người dùng có một phần tử mẹ và có thể có nhiều phần tử con. Mỗi phần tử đều nằm trong phần tử mẹ, được xác định theo vị trí (x, y) và kích thước, được chỉ định là widthheight.

Phần tử mẹ xác định các quy tắc ràng buộc cho các phần tử con. Một phần tử được yêu cầu xác định kích thước riêng trong các ràng buộc đó. Các ràng buộc hạn chế widthheight tối thiểu và tối đa của một phần tử. Nếu một phần tử có các phần tử con thì phần tử đó có thể đo lường từng phần tử con để giúp xác định kích thước của phần tử đó. Sau khi một phần tử xác định và báo cáo kích thước riêng, phần tử này có cơ hội xác định cách đặt các phần tử con tương ứng với nó, như mô tả chi tiết trong phần Tạo bố cục tuỳ chỉnh.

Việc sắp xếp từng nút trong cây giao diện người dùng là quá trình 3 bước. Mỗi nút phải:

  1. Đo lường mọi phần tử con
  2. Quyết định kích thước riêng
  3. Đặt phần tử con của mình

Ba bước của bố cục nút: đo lường phần tử con, quyết định kích thước, đặt phần tử con

Việc sử dụng phạm vi giúp xác định thời điểm bạn có thể đo lường và đặt thành phần con. Bạn chỉ đo lường được bố cục trong quá trình chuyển bố cục và đo lường, đồng thời chỉ có thể đặt thành phần con trong quá trình chuyển bố cục (và chỉ sau khi thành phần con đó được đo lường). Do các phạm vi của Compose như MeasureScope! và PlacementScope, quá trình này được thực thi tại thời điểm biên dịch.

Sử dụng đối tượng sửa đổi bố cục

Bạn có thể sử dụng đối tượng sửa đổi layout để sửa đổi cách đo lường và bố trí một phần tử. Layout là lambda - thông số của giá trị này bao gồm phần tử mà bạn có thể đo lường được chuyển dưới dạng measurable và các giới hạn sắp tới của những yếu tố có thể kết hợp, được chuyển dưới dạng constraints. Công cụ sửa đổi bố cục tuỳ chỉnh có thể có dạng như sau:

fun Modifier.customLayoutModifier() =
    layout { measurable, constraints ->
        // ...
    }

Chúng ta hãy hiển thị một Text trên màn hình và kiểm soát khoảng cách từ trên cùng đến đường cơ sở của dòng văn bản đầu tiên. Đây chính xác là những gì paddingFromBaseline sửa đổi, chúng tôi sẽ triển khai ở đây làm ví dụ. Để thực hiện điều này, hãy sử dụng công cụ sửa đổi layout để tự đặt yếu tố có thể kết hợp trên màn hình. Dưới đây là hành vi mong muốn, trong đó khoảng đệm trên Text được đặt 24.dp:

Thể hiện sự khác biệt giữa khoảng đệm giao diện người dùng thông thường (vốn đặt khoảng trống giữa các phần tử) và khoảng đệm văn bản giúp đặt khoảng trống từ một đường cơ sở sang đường cơ sở tiếp theo

Dưới đây là mã tạo khoảng trống đó:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }
}

Sau đây là những gì xảy ra trong mã đó:

  1. Trong tham số lambda measurable, bạn đo lường Text (do tham số có thể đo lường đại diện) bằng cách gọi measurable.measure(constraints).
  2. Bạn chỉ định kích thước của thành phần kết hợp bằng cách gọi phương thức layout(width, height). Phương thức này cũng cung cấp một lambda dùng để đặt các thành phần được gói. Trong trường hợp này, đó là chiều cao giữa đường cơ sở cuối cùng và khoảng đệm trên cùng được thêm vào.
  3. Bạn định vị các thành phần được bao bọc trên màn hình bằng cách gọi placeable.place(x, y). Nếu không được đặt thì các phần tử được bao bọc sẽ không hiển thị. Vị trí ytương ứng với khoảng đệm trên cùng – vị trí của đường cơ sở đầu tiên của văn bản.

Để xác minh tính năng này hoạt động như dự kiến, hãy sử dụng công cụ sửa đổi này trên Text:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

Nhiều bản xem trước thành phần văn bản; một bản xem trước hiển thị khoảng đệm thông thường giữa các thành phần, bản xem trước còn lại hiển thị khoảng đệm từ một đường cơ sở này đến đường cơ sở tiếp theo

Tạo bố cục tuỳ chỉnh

Đối tượng sửa đổi layout chỉ thay đổi các thành phần kết hợp thực hiện lệnh gọi. Để đo lường và bố cục nhiều thành phần kết hợp, hãy sử dụng thành phần kết hợp Layout. Thành phần kết hợp này có thể giúp bạn đo lường và bố trí thành phần con theo cách thủ công. Tất cả bố cục cấp cao hơn như ColumnRow được tạo bằng thành phần kết hợp Layout.

Hãy tạo một phiên bản Column rất cơ bản. Hầu hết các bố cục tuỳ chỉnh đều theo mẫu sau:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
        // ...
    }
}

Tương tự như công cụ sửa đổi layout, measurables là danh sách các yếu tố con cần đo lường và constraints là các hạn chế từ yếu tố gốc. Theo nguyên lý tương tự như trước đây, bạn có thể triển khai MyBasicColumn như sau:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

Các yếu tố có thể kết hợp con có thể bị hạn chế do các quy tắc ràng buộc của Layout (không có quy tắc ràng buộc của minHeight) và chúng được đặt dựa trên yPosition của yếu tố có thể kết hợp trước đó.

Dưới đây là cách sử dụng yếu tố có thể kết hợp tuỳ chỉnh:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text("MyBasicColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

Trong một cột, có nhiều thành phần văn bản được xếp chồng lên trên thành phần tiếp theo.

Hướng bố cục

Thay đổi hướng bố cục của một yếu tố có thể kết hợp bằng cách thay đổi thành phần LocalLayoutDirection trên thiết bị.

Nếu bạn đặt thành phần kết hợp lên màn hình theo cách thủ công, thì LayoutDirection sẽ thuộc LayoutScope của đối tượng sửa đổi layout hoặc thành phần kết hợp Layout.

Khi sử dụng layoutDirection, hãy đặt yếu tố có thể kết hợp bằng cách sử dụng place. Không giống như phương thức placeRelative, place không thay đổi theo hướng bố cục (từ trái sang phải so với từ phải sang trái).

Bố cục tuỳ chỉnh trong thực tế

Hãy tìm hiểu thêm về bố cục và đối tượng sửa đổi trong phần Bố cục cơ bản trong Compose, cũng như xem bố cục tuỳ chỉnh thực tế trong phần Mẫu Compose tạo bố cục tuỳ chỉnh.

Tìm hiểu thêm

Để tìm hiểu thêm về bố cục tuỳ chỉnh trong Compose, hãy tham khảo thêm những tài nguyên sau đây.

Video